Daemion docs

How do I create a job extension?

A job extension is an autonomous work unit — a prompt, an agent, and a trigger — that runs on a schedule or fires on demand without you being in the conversation.


When would I use a job?

Use a job when you want work to happen automatically while you’re away. Good candidates:

  • Scheduled briefings — a daily 8am digest of overnight activity, Slack messages, or news
  • Health checks — ping your server every hour and write findings to Engram
  • Recurring synthesis — weekly summary of everything stored in the knowledge graph
  • Chained pipelines — a research job that triggers a formatting job on completion
  • Manual triggers — one-tap jobs you fire from the UI but don’t want to re-describe each time

Jobs are not for real-time interactive responses — use a command extension or just chat for that.


How do I create one by chatting?

Describe the schedule and the work. Daemion handles the rest:

You: Create a job that checks my server health every hour

Daemion: I’ll set up a server-health-check job for you.

The job is created with a cron trigger of 0 * * * * (top of every hour). It pings your server endpoints, logs the response times and status codes, and stores a summary in Engram. If anything is down, it flags it prominently. The job starts disabled — enable it when you’re ready.

You: Enable it and also write results to a file at ~/logs/health.md

Daemion: Done — job enabled, output now includes file: ~/logs/health.md. First run is at the top of the next hour.


How do I create one via the API?

Jobs created via the API start with enabled: false unless you explicitly set it. Enable via PATCH /extensions/:id or toggle in the app.

bash
Verified

curl -X POST http://localhost:3001/extensions
-H “Authorization: Bearer $DAEMION_TOKEN”
-H “Content-Type: application/json”
-d ’{ “type”: “job”, “name”: “server-health-check”, “description”: “Ping server endpoints every hour and store results.”, “definition”: { “name”: “server-health-check”, “description”: “Check server health endpoints and log status.”, “enabled”: true, “trigger”: { “type”: “cron”, “schedule”: “0 * * * *” }, “agent”: “haiku”, “outputs”: [ { “engram”: true }, { “file”: ”~/logs/health.md” } ], “priority”: “normal”, “max_duration”: 120 }, “source”: “user”, “enabled”: true }‘


What does the job schema look like?

Job extensions use GenericDefinitionSchema at the extension envelope level, but the definition field is validated against JobSchema from src/schema/job.ts. The full shape:

typescript

// JobDefinition — the shape of definition for type: “job” // Validated against JobSchema in src/schema/job.ts interface JobDefinition { name: string; // kebab-case, matches extension name description: string; // required, min 1 char

enabled: boolean; // default true inside definition; extension.enabled controls runtime

trigger: | { type: “cron”; schedule: string } // standard cron expression, evaluated by croner | { type: “manual” } // never fires on schedule; run via POST /jobs/:name/run | { type: “chain”; after: string }; // fires when the named job completes

agent?: string; // agent name to run the job, e.g. “haiku”, “sonnet”, “opus”

context: Array< | { engram: string } // recall query to run against Engram before the job starts | { read: string } // file path to read and inject into context | { job: string } // output of another job to inject >;

outputs: Array< | { file: string } // write output to this file path | { engram: true } // store output in the knowledge graph | { slack: string } // post to this Slack channel | { chain: string } // trigger this job name after completion >;

chains: string[]; // additional job names to trigger on completion

priority: “low” | “normal” | “high” | “urgent”; // default “normal” max_duration: number; // seconds, default 300 (5 minutes) }

The trigger.schedule field uses standard 5-field cron syntax, evaluated by the croner library (see src/core/triggers.ts). The engine calls shouldJobFire() at each heartbeat tick and fires any job whose schedule matches the current time.


Frequently asked questions

Q How do chain triggers work?
Set trigger: { type: "chain", after: "parent-job-name" }. The engine fires the chained job immediately after the parent completes — not on a clock schedule. Chain triggers are handled directly in src/core/engine.ts after the parent finishes. They do not appear in shouldJobFire() (which handles cron only).
Q Which agent should I assign to a job?
Use haiku for frequent, lightweight tasks (health checks, classification, quick summaries) where cost matters. Use sonnet for research, writing, and moderate reasoning. Use opus sparingly — for judgment-heavy jobs like architectural review or decisions where thoroughness justifies the cost.
Q How do I run a job immediately without waiting for the schedule?
POST to /run/:job with your bearer token to trigger a manual run. Jobs with trigger.type: "manual" only run this way — they never fire on a schedule.
Q What cron syntax does Daemion support?
Standard 5-field cron: minute hour day month weekday. Examples: 0 8 * * 1-5 (8am weekdays), 0 * * * * (every hour), */15 * * * * (every 15 minutes). The croner library is used for evaluation — see its docs for extended syntax support.
Q Can a job write to both Engram and a file?
Yes — outputs is an array and all entries are applied. A job can write to a file, store in Engram, post to Slack, and chain another job in a single run. Each output entry is processed after the agent finishes.

What can go wrong

What can go wrong

400 &#123;"error": "validation failed"&#125; — The definition failed JobSchema validation. Common causes: name doesn’t match kebab-case regex (^[a-z][a-z0-9-]*$), trigger.schedule is missing for a cron trigger, or priority is not one of low | normal | high | urgent.

Job never fires — Check two things: (1) extension.enabled must be true at the extension envelope level, and (2) definition.enabled must be true inside the job definition. Both must be true for the engine to schedule it.

403 &#123;"error": "cannot disable essential extensions"&#125; — Essential built-in extensions cannot be disabled.

401 &#123;"error": "unauthorized"&#125; — Bearer token missing or incorrect. Check ~/.daemion/.gateway-token and ensure $DAEMION_TOKEN is set in your shell.

Chain job fires before parent output is available — Chain triggers fire immediately on parent completion. If your chained job reads the parent’s file output, the file write happens first — but if it queries Engram, allow a brief moment for the Engram store to commit. Use context: [&#123; "job": "parent-job-name" &#125;] to inject parent output directly rather than re-querying.

max_duration exceeded — Jobs that run longer than max_duration seconds are stopped by the engine. The default is 300s (5 minutes). Increase this for long-running research or synthesis jobs, but keep Haiku jobs under 60s.


What’s next?