Skip to content

Spored Stable โ€‹

What it is. Spored is the lifecycle daemon that runs on every instance launched by spawn. It's a small binary provisioned automatically at launch โ€” you never install or run it manually.

When you meet it. You don't invoke spored from your laptop; spawn provisions it and it works on its own. You only interact with it directly when debugging on an instance (sudo systemctl status spored, spored status, spored complete).

Why it matters. It's the reason auto-termination survives your laptop closing: spored enforces the lifecycle from inside the instance โ€” terminate on TTL, stop or hibernate on idle, act on a completion signal, and clean up DNS/notifications before the instance disappears. Its rules come from the instance's EC2 tags, which it re-reads about once a minute. See Costs & safety guarantees for what happens if spored itself fails.

What spored does โ€‹

Once started, spored runs a check every minute:

  1. TTL โ€” if the absolute deadline (spawn:ttl-deadline tag) has passed, terminate.
  2. Completion signal โ€” if the sentinel file (/tmp/SPAWN_COMPLETE by default) exists, take the configured action.
  3. Cost limit โ€” if accumulated compute spend (rate ร— total compute time, carried across stop/resume) has exceeded spawn:cost-limit, terminate.
  4. Idle detection โ€” if all activity signals have been quiet for spawn:idle-timeout, stop (or hibernate).

Spot interruption notices are polled separately every 5 seconds and acted on immediately.

Versioning โ€‹

Spored is built and released in lockstep with spawn โ€” the same version tag, the same GoReleaser run. Every spawn release publishes the matching spored binary (Linux amd64/arm64) to regional S3 buckets, and spawn launch provisions it onto the instance automatically. There is no separate spored version to track or install: spored version on an instance reports the spawn release it shipped with.

Subcommands โ€‹

Spored is invoked directly on the instance (not from your laptop):

spored                  Run as daemon (systemd starts this automatically)
spored status           Show lifecycle state, TTL, cost, and metrics
spored reload           Re-read EC2 tags without restarting the daemon
spored config get <key> Query a single configuration value
spored config set <key> <value>  Update a tag and reload
spored config list      Show all configuration
spored complete         Signal that your workload finished
spored version          Print version

spored status โ€‹

Shows the current lifecycle state of the instance:

  my-job  (i-0abc123def456xyz)
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

  Started:          2026-05-18 10:00 UTC
  Elapsed:          6h 30m (5h compute ยท 1h 30m stopped)
  TTL:              2h 30m remaining  (terminates 2026-05-18 19:00 UTC)
  Idle timeout:     1h  (currently active)
  On complete:      terminate (watching /tmp/SPAWN_COMPLETE)

  Compute cost:     $1.44  (5h ร— $0.2400/hr)
  Storage cost:     $0.18  (1h 30m ร— $0.1200/hr EBS)
  Cumulative cost:  $1.62
  Effective rate:   $0.2496/hr  (4% lower than continuous on-demand)
  Cost limit:       $50.00  ($1.44 compute used, 3% โ€” $48.56 remaining; compute-only)

  CPU:              2.5%
  Network:          45 B/min
  Pre-stop hook:    aws s3 sync /results s3://bucket/run-001

Cost figures are estimates; definitive billing comes from AWS.

spored config get/set/list โ€‹

Read or update lifecycle settings without SSH-ing in โ€” spawn instance-config calls these via SSH from your laptop. You can also call them directly on the instance:

sh
spored config list
spored config get idle-timeout
spored config set ttl 12h
spored config set on-complete stop

Valid keys: ttl, idle-timeout, on-complete, hibernate, completion-file, completion-delay

set updates the corresponding EC2 tag and calls spored reload.

spored complete โ€‹

Signal that your workload finished. Writes the completion file to trigger the spawn:on-complete action.

sh
spored complete                                     # empty file
spored complete --status success
spored complete --status success --message "1000 jobs done"
spored complete --file /var/run/myapp/done          # custom path

After the file appears, spored waits spawn:completion-delay (default 30s), then takes the spawn:on-complete action: terminate, stop, or hibernate.


EC2 tags reference โ€‹

All spored configuration is stored as EC2 tags. Spawn writes them at launch; you can read them with spored config list and update writable ones with spored config set or directly via the EC2 API.

Lifecycle โ€‹

TagTypeExampleNotes
spawn:ttlduration24hMaximum lifetime from launch.
spawn:ttl-deadlineRFC33392026-05-19T14:00:00ZAuthoritative deadline โ€” set once at launch, never resets across stop/wake cycles. Takes precedence over spawn:ttl.
spawn:launch-timeRFC33392026-05-18T10:00:00ZOriginal launch time, anchors the deadline.
spawn:idle-timeoutduration1hStop (or hibernate) after this duration of inactivity.
spawn:hibernate-on-idlebooltrueHibernate on idle instead of stop. Default: false.
spawn:on-completeenumterminateAction on completion: terminate, stop, hibernate.
spawn:completion-filepath/tmp/SPAWN_COMPLETESentinel file path to watch. Default: /tmp/SPAWN_COMPLETE.
spawn:completion-delayduration30sGrace period after completion file appears. Default: 30s.
spawn:pre-stopshell commandaws s3 sync /results s3://...Runs before any lifecycle-triggered stop/terminate.
spawn:pre-stop-timeoutduration5mMax time for the pre-stop command. Default: 5m (Spot: 90s).

Idle detection โ€‹

TagTypeDefaultDescription
spawn:idle-cpufloat (%)5.0CPU usage below this threshold counts as idle.
spawn:active-portscomma-separated intsTCP ports to monitor โ€” active connections prevent idle termination (e.g., 8787 for RStudio).
spawn:active-processescomma-separated stringsProcess names to check via pgrep โ€” if any are running, not idle (e.g., rsession,jupyter).

Cost tracking โ€‹

TagTypeDescription
spawn:price-per-hourfloatOn-demand hourly rate โ€” written at launch for cost display.
spawn:ebs-hourly-costfloatEBS volume cost/hr โ€” looked up once by spored on first start and cached here.
spawn:compute-secondsintCumulative compute seconds โ€” updated by spored every few minutes.
spawn:cost-limitfloatTerminate when total compute spend reaches this amount (USD). Compute-only (excludes EBS/storage); accumulates across stop/resume.

Notifications and DNS โ€‹

TagTypeDescription
spawn:notify-urlURLspore-bot Lambda endpoint for Slack/Teams lifecycle notifications.
spawn:slack-workspace-idstringWorkspace ID for routing notifications.
spawn:dns-namestringDNS subdomain โ€” spored registers on start, deregisters on shutdown.

Idle detection โ€‹

Spored checks every minute. An instance is active (not idle) if any of these are true:

SignalThresholdDetail
SSH sessionsanyw -h -s โ€” any user with keyboard activity in the last 5 minutes
Active processesany runningProcesses listed in spawn:active-processes detected via pgrep
Port connectionsanyESTABLISHED TCP connections on ports listed in spawn:active-ports
CPU usageโ‰ฅ 5%Measured as a delta since the last check (configurable via spawn:idle-cpu)
Network trafficโ‰ฅ 100 KB/minRX+TX on primary interface, measured from /proc/net/dev
Disk I/Oโ‰ฅ 100 KB/minRead+write on block devices, measured from /proc/diskstats
GPU utilizationโ‰ฅ 5%nvidia-smi โ€” only checked if NVIDIA GPU is present
Active PTYsanyOpen pseudo-terminal entries in /dev/pts/

The idle timer starts from the moment all signals are inactive simultaneously. Any single active signal resets it.


Completion file โ€‹

When --on-complete is set at launch, spored watches for the completion file and acts when it appears.

Default path: /tmp/SPAWN_COMPLETE

Creating it from your job script:

sh
# Option 1: empty file
touch /tmp/SPAWN_COMPLETE

# Option 2: with metadata (recommended)
spored complete --status success --message "Finished 1000 parameter combinations"

# Option 3: custom path (must match --completion-file at launch)
spored complete --file /var/run/myapp/done

File format: optional JSON metadata โ€” spored reads and logs it, then acts regardless of content:

json
{
  "status": "success",
  "message": "All tasks done",
  "timestamp": "2026-05-18T14:30:00Z"
}

Sequence after file appears:

  1. Slack/Teams notification sent immediately
  2. Users warned via wall
  3. spawn:completion-delay grace period (default 30s)
  4. spawn:pre-stop command runs (if set)
  5. spawn:on-complete action: terminate, stop, or hibernate

Lifecycle events and notifications โ€‹

When --slack-workspace is set at launch, spored sends a DM for each event:

EventTrigger
TTL warning5 minutes before TTL deadline
TTL expiredTTL deadline reached โ€” terminating
Idle warning5 minutes before idle timeout triggers
Idle stoppedIdle timeout triggered โ€” stopping
Idle hibernatedIdle timeout triggered โ€” hibernating
CompletionCompletion file detected
Spot interruptionAWS 2-minute Spot reclaim notice
Cost limit warningAccumulated spend โ‰ฅ 90% of spawn:cost-limit

Logs and diagnostics โ€‹

sh
# View daemon logs
journalctl -u spored -n 100
journalctl -u spored -f

# Raw log file
tail -f /var/log/spored.log

# Full status with cost breakdown
spored status

# Check what tags spored is reading
aws ec2 describe-tags \
  --filters "Name=resource-id,Values=$(ec2-metadata --instance-id | cut -d' ' -f2)" \
  --query 'Tags[?starts_with(Key, `spawn:`)].{K:Key,V:Value}' \
  --output table

Environment variables โ€‹

VariableDefaultDescription
SPORED_TAG_PREFIXspawnEC2 tag prefix โ€” change this for multi-tenant deployments
SPORED_DNS_DOMAINspore.hostDNS domain base โ€” change for self-hosted deployments