Skip to content

Agent Guide

The Crontinel agent is a lightweight daemon that runs on your server, connects to app.crontinel.com, and polls for remote commands. It enables cloud-triggered cron execution — schedule a command from the dashboard and the agent runs it on your server.

How It Works

  1. The agent registers with Crontinel Cloud using your API key and app ID
  2. It connects to receive commands — see Connection Method below, since this differs by runtime
  3. When a trigger is scheduled from the dashboard, the agent checks the command against your configured allowlist
  4. If allowed, it executes the command and reports the result (success/failure, output, duration) back to the cloud; if not allowed, it reports a rejection and does not run it
  5. A heartbeat is sent every 60 seconds to confirm the agent is alive

Connection Method

The Laravel agent and the Node.js/Python agents use different transports to receive commands:

  • Laravel opens a persistent SSE (Server-Sent Events) connection to /v1/agent/stream and receives commands as they’re dispatched, with no polling interval. Heartbeats are sent separately via /v1/agent/heartbeat every 60 seconds.
  • Node.js and Python poll app.crontinel.com/api/v1/agents/{id}/commands every 5 seconds, and send heartbeats via /api/v1/agents/heartbeat every 60 seconds.

Both approaches deliver the same behavior from your perspective — commands run within seconds of being scheduled — the underlying connection just differs by runtime.

Laravel Agent

The Laravel package (crontinel/laravel) includes a built-in agent command.

Prerequisites

  • crontinel/laravel installed via Composer
  • CRONTINEL_API_KEY and CRONTINEL_APP_ID set in .env

Start the Agent

Terminal window
php artisan crontinel:agent

Production Setup

Generate a systemd unit file:

Terminal window
php artisan crontinel:agent --systemd

Or a supervisor config:

Terminal window
php artisan crontinel:agent --supervisor

Node.js Agent

The Node package (@crontinel/node) includes a CLI agent.

Prerequisites

  • @crontinel/node installed via npm
  • CRONTINEL_API_KEY and CRONTINEL_APP_ID environment variables set

Start the Agent

Terminal window
npx crontinel agent

Or with environment variables inline:

Terminal window
CRONTINEL_API_KEY=your-key CRONTINEL_APP_ID=your-app npx crontinel agent

Python Agent

The Python package includes an agent CLI (requires PyPI installation when available).

Prerequisites

  • crontinel package installed
  • CRONTINEL_API_KEY and CRONTINEL_APP_ID environment variables set

Start the Agent

Terminal window
crontinel agent

Command Allowlisting

The agent will only execute commands that match an entry in your allowlist. An unconfigured or empty allowlist means no commands are permitted — this is the default, fail-closed behavior.

Patterns support exact string matches and * as a wildcard (e.g. php artisan queue:* allows any queue: subcommand).

Laravel — publish the config (php artisan vendor:publish --tag=crontinel-config) and set allowed_commands in the agent block of config/crontinel.php:

'agent' => [
// ...
'allowed_commands' => [
'php artisan queue:restart',
'php artisan horizon:terminate',
'php artisan queue:*',
],
],

Node.js and Python — set the CRONTINEL_AGENT_ALLOWED_COMMANDS environment variable to a comma-separated list of patterns:

Terminal window
CRONTINEL_AGENT_ALLOWED_COMMANDS="php artisan queue:restart,php artisan horizon:terminate,queue:*" npx crontinel agent

The Node.js and Python agents also accept an equivalent allowedCommands / allowed_commands option when constructing the agent programmatically.

A rejected command is reported back to the dashboard as failed with the reason “Command rejected: not in allowlist,” so you can see rejections in your trigger history and adjust the allowlist as needed.

Systemd Service (All Runtimes)

Create /etc/systemd/system/crontinel-agent.service:

[Unit]
Description=Crontinel Agent Daemon
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/your/app
Environment=CRONTINEL_API_KEY=your-api-key
Environment=CRONTINEL_APP_ID=your-app-slug
ExecStart=/usr/bin/php artisan crontinel:agent
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

Enable and start:

Terminal window
sudo systemctl enable crontinel-agent
sudo systemctl start crontinel-agent

Supervisor Config (All Runtimes)

Create /etc/supervisor/conf.d/crontinel-agent.conf:

[program:crontinel-agent]
command=php artisan crontinel:agent
directory=/path/to/your/app
user=www-data
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/crontinel-agent.err.log
stdout_logfile=/var/log/crontinel-agent.out.log

Reload and start:

Terminal window
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start crontinel-agent

Environment Variables

VariableRequiredDefaultDescription
CRONTINEL_API_KEYYes—Your API key from app.crontinel.com/settings
CRONTINEL_APP_IDYes—Your app slug from the app settings page
CRONTINEL_API_URLNohttps://app.crontinel.comAPI base URL (change only for self-hosted)
CRONTINEL_AGENT_ALLOWED_COMMANDSNode/Python only— (fail-closed)Comma-separated allowlist patterns. Laravel uses allowed_commands in config/crontinel.php instead — see Command Allowlisting

Testing

  1. Add the command you want to test to your allowlist — untested commands are rejected by default
  2. Go to your app detail page
  3. Click “Schedule Trigger”
  4. Enter the same command (e.g., php artisan inspire)
  5. Set it to run “Now”
  6. Watch the agent execute it and report back

Troubleshooting

Agent won’t start

  • Verify CRONTINEL_API_KEY and CRONTINEL_APP_ID are set
  • Check network connectivity to app.crontinel.com

Agent starts but no commands received

  • Verify the app ID matches your app in the dashboard
  • Check that triggers are being dispatched (Dashboard → Triggers)

Agent crashes repeatedly

  • Enable logging: set CRONTINEL_AGENT_LOG=/var/log/crontinel-agent.log
  • Ensure the agent user has permission to execute the scheduled commands

Commands are rejected / never execute

  • Check the trigger’s result on the dashboard for “Command rejected: not in allowlist”
  • Verify the command matches an entry in your allowlist exactly, or via a * pattern
  • Remember the default allowlist is empty — nothing runs until you configure one