{
  "schemaVersion": "1.0",
  "title": "Hermes scheduled job capabilities",
  "description": "Use these eight endpoint-qualified capabilities to manage jobs in one Hermes profile's persistent scheduler.",
  "canonical": "https://getaip.org/docs/connectors/hermes-agent/capabilities/scheduled-jobs",
  "route": "/docs/connectors/hermes-agent/capabilities/scheduled-jobs",
  "source": "docs/connectors/hermes-agent/capabilities/scheduled-jobs.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/hermes-agent/capabilities/scheduled-jobs.md",
    "txt": "/docs/download/connectors/hermes-agent/capabilities/scheduled-jobs.txt",
    "json": "/docs/download/connectors/hermes-agent/capabilities/scheduled-jobs.json",
    "pdf": "/docs/download/connectors/hermes-agent/capabilities/scheduled-jobs.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Hermes scheduled job capabilities\ndescription: >-\n  Create and govern persisted Hermes jobs without confusing an accepted\n  scheduling request with completed execution or delivery\nkind: capability-reference\naudience: operator\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: hermes-agent\ncapabilityIds:\n  - cap:hermes_agent:<endpoint_id>:jobs_list\n  - cap:hermes_agent:<endpoint_id>:job_create\n  - cap:hermes_agent:<endpoint_id>:job_get\n  - cap:hermes_agent:<endpoint_id>:job_update\n  - cap:hermes_agent:<endpoint_id>:job_delete\n  - cap:hermes_agent:<endpoint_id>:job_pause\n  - cap:hermes_agent:<endpoint_id>:job_resume\n  - cap:hermes_agent:<endpoint_id>:job_run\n---\n\n# Hermes scheduled job capabilities\n\nUse these eight endpoint-qualified capabilities to manage jobs in one Hermes\nprofile's persistent scheduler.\n\nAn accepted AIP action confirms that Hermes stored or changed scheduler state.\nIt does not confirm that a job ran, produced output, or delivered a message.\nIn particular, `job_run` only makes a job due for later scheduler execution.\n\nA scheduled job can invoke an agent, tools, scripts, providers, and delivery\nadapters configured outside this API. Inspect a job before approving any\noperation that creates, changes, resumes, runs, or deletes it.\n\n## Operations\n\nReplace `<endpoint_id>` with the normalized endpoint ID returned by AIP\ndiscovery.\n\n| Operation | Provider request | Kind | Risk | Approval | Key | Retry |\n|---|---|---|---|---|---|---|\n| `jobs_list` | `GET /api/jobs` | Tool | Low | No | Optional | Safe |\n| `job_create` | `POST /api/jobs` | Tool | Medium | Yes | Required | Unsafe |\n| `job_get` | `GET /api/jobs/{job_id}` | Tool | Low | No | Optional | Safe |\n| `job_update` | `PATCH /api/jobs/{job_id}` | Tool | Medium | Yes | Required | Unsafe |\n| `job_delete` | `DELETE /api/jobs/{job_id}` | Tool | High | Yes | Required | Unsafe |\n| `job_pause` | `POST /api/jobs/{job_id}/pause` | Tool | Medium | Yes | Required | Unsafe |\n| `job_resume` | `POST /api/jobs/{job_id}/resume` | Tool | Medium | Yes | Required | Unsafe |\n| `job_run` | `POST /api/jobs/{job_id}/run` | Agent | High | Yes | Required | Unsafe |\n\nAll eight capabilities are stable. They require the selected endpoint's\nBearer credential. Input cannot select a different endpoint or provider URL.\n\nEvery path operation requires a 12-character lowercase hexadecimal `job_id`,\nfor example `a1b2c3d4e5f6`. A name, uppercase ID, shortened ID, or arbitrary UUID\nis rejected by the connector before the provider call.\n\n## Choose a schedule\n\nHermes parses four schedule forms.\n\n| Intent | Example | Stored kind | First eligible time |\n|---|---|---|---|\n| Run once after a delay | `30m`, `2h`, `1d` | `once` | Creation time plus the delay |\n| Run at an interval | `every 30m`, `every 2h` | `interval` | Creation time plus the interval |\n| Follow a cron expression | `0 9 * * *` | `cron` | Next matching occurrence |\n| Run once at a timestamp | `2030-01-15T09:00:00+04:00` | `once` | The supplied instant |\n\nA timestamp without an offset is interpreted in the configured Hermes\ntimezone. Supply an explicit offset when an application must preserve one\nunambiguous instant across deployments.\n\nA one-shot more than 120 seconds in the past is rejected. A one-shot created\nwithout `repeat` receives a run limit of one. Use recurring interval or cron\nschedules for repeated work.\n\nCron expressions depend on the provider's `croniter` installation. The pinned\nHermes source treats it as a runtime dependency, but an unavailable dependency\nstill makes expression parsing or next-run computation fail.\n\n## Shared AIP contract\n\n| Contract field | List and get | Create and update | Pause and resume | Delete | Run |\n|---|---|---|---|---|---|\n| Side effects | `read`, `external_network` | `read`, `write`, `external_network` | `read`, `write`, `external_network` | `read`, `delete`, `external_network` | `read`, `write`, `external_network` |\n| Published data class | Internal; no PII | Confidential; contains PII | Internal; contains PII | Restricted; contains PII | Confidential; contains PII |\n| Redaction | Required | Required | Required | Required | Required |\n| Idempotency | Optional | Required | Required | Required | Required |\n| Retry support | Yes | No | No | No | No |\n| Retry safety | Safe | Unsafe | Unsafe | Unsafe | Unsafe |\n| Completion | Sync | Sync | Sync | Sync | Sync request; delayed job |\n| Compensation | Rollback not supported | Rollback not supported | Rollback not supported | Rollback not supported | Rollback not supported |\n\nAll eight contracts declare a 60-second timeout, 5-second expected latency,\n10-second maximum queue-delay hint, and `99.9%` availability target. The\nconnector applies a 30-second HTTP timeout to each JSON request. Trusted\nexecution is also bounded by the AIP deadline and cancellation token.\n\nThese values describe the control request only. They do not bound the later\njob execution. The availability value is a declaration, not observed service\nor qualification evidence.\n\nThe six mutations require tenant-policy approval. Approval lasts 15 minutes\nand records a reason, input snapshot, and policy decision. No operation\npublishes transaction support, and no compensation capability is available.\n\nThe `job_run` contract understates possible downstream effects. A stored job\ncan later send a message or execute code even though the capability declares\nonly read, write, and external-network effects. Base policy on the retrieved\njob and the endpoint's Hermes configuration, not that generic list alone.\n\n## List jobs\n\n`jobs_list` returns enabled jobs by default:\n\n```json\n{}\n```\n\nInclude paused and otherwise disabled records when reconciling scheduler state:\n\n```json\n{\n  \"include_disabled\": true\n}\n```\n\nThe provider returns one object containing a `jobs` array. There is no limit,\ncursor, offset, total count, or documented sort. Results retain storage order.\n\nThe connector also accepts a `query` object and forwards scalar values. Prefer\nthe typed top-level `include_disabled` field. Supplying the same key in both\nplaces creates duplicate query parameters with provider-dependent selection.\n\nThe response contains full normalized job records rather than a safe summary.\nA record can include its prompt, skills, model and provider routing, delivery\ntarget, origin network metadata, schedule, timestamps, errors, and execution\nstate. Jobs created by other Hermes surfaces can expose fields that AIP cannot\nset.\n\nThe list is profile-scoped, not caller-scoped. It covers every job in the\nselected endpoint's active `HERMES_HOME`, including jobs created outside AIP.\n\n## Create a job\n\nUse top-level fields for the frozen AIP schema:\n\n```json\n{\n  \"name\": \"Reconcile support queue\",\n  \"schedule\": \"every 30m\",\n  \"prompt\": \"Summarize unresolved queue items and record the totals.\",\n  \"deliver\": \"local\",\n  \"skills\": [\"support-reporting\"],\n  \"repeat\": 24\n}\n```\n\n| Field | Requirement | Frozen AIP bound |\n|---|---|---|\n| `name` | Required, non-empty string | 200 characters |\n| `schedule` | Required, non-empty string | 512 characters |\n| `prompt` | Optional string | 5,000 characters |\n| `deliver` | Optional string | 256 characters |\n| `skills` | Optional string array | 256 items, 256 characters each |\n| `repeat` | Optional positive integer | At least one |\n\nThe schema rejects other top-level fields. Although a `body` property exists\nfor transport compatibility, mixing it with typed fields can cause the wrapper\nto replace those fields before the provider call. Use the typed form above.\n\nThe provider trims `name` and `schedule`, parses the schedule, and generates a\n12-hex job ID. When the optional prompt scanner imports successfully, it also\nrejects blocked patterns in a non-empty prompt. An API-created job records\n`api_server` origin metadata and defaults delivery to `local`.\n\nHermes stores the initial job as enabled with state `scheduled`. It calculates\n`next_run_at`, initializes run counts and status, and returns:\n\n```json\n{\n  \"job\": {\n    \"id\": \"a1b2c3d4e5f6\",\n    \"name\": \"Reconcile support queue\",\n    \"schedule\": {\n      \"kind\": \"interval\",\n      \"minutes\": 30,\n      \"display\": \"every 30m\"\n    },\n    \"schedule_display\": \"every 30m\",\n    \"repeat\": {\n      \"times\": 24,\n      \"completed\": 0\n    },\n    \"enabled\": true,\n    \"state\": \"scheduled\",\n    \"last_run_at\": null,\n    \"last_status\": null,\n    \"last_error\": null,\n    \"last_delivery_error\": null,\n    \"deliver\": \"local\"\n  }\n}\n```\n\nThe actual response contains more fields. Treat the returned ID as the only\nvalid identifier for later AIP operations.\n\nPut the required idempotency key on the AIP Action, not in this JSON. The\nconnector forwards it as `Idempotency-Key`; the pinned provider handler does\nnot interpret that header.\n\n## Inspect one job\n\nUse `job_get` before every governed mutation:\n\n```json\n{\n  \"job_id\": \"a1b2c3d4e5f6\"\n}\n```\n\nThe provider returns `{\"job\": <record>}` or `404` when the ID is absent.\nInspect at least these fields:\n\n- `enabled`, `state`, `next_run_at`, and `schedule` for eligibility;\n- `prompt`, `skills`, and any script or toolset fields for execution scope;\n- `provider`, `model`, and delivery fields for external destinations;\n- `repeat`, `last_run_at`, `last_status`, and errors for prior outcomes.\n\nAn origin record is audit context. It is not proof that the current caller\ncreated or owns the job.\n\n## Update safe fields\n\n`job_update` accepts a path ID and a partial body:\n\n```json\n{\n  \"job_id\": \"a1b2c3d4e5f6\",\n  \"name\": \"Reconcile priority support queue\",\n  \"schedule\": \"every 1h\",\n  \"prompt\": \"Summarize unresolved priority items and record the totals.\"\n}\n```\n\nThe provider whitelists `name`, `schedule`, `prompt`, `deliver`, `skills`,\nlegacy `skill`, `repeat`, and `enabled`. Other provider fields are discarded.\nAn effective patch with no whitelisted field returns `400`.\n\nA changed schedule is reparsed. If the job is not paused, Hermes recalculates\nits next eligible time. A non-empty changed prompt is scanned again.\n\nDo not patch `repeat` in the pinned version. The REST handler forwards an\ninteger, but the scheduler later expects an object with `times` and\n`completed`. This source-level shape mismatch can leave the job unreadable to\nexecution paths. To change the limit:\n\n1. Pause and inspect the old job.\n2. Create a replacement with the intended schedule and `repeat` value.\n3. Inspect the replacement and then delete the old job.\n\nDo not use a direct `enabled` patch as a substitute for pause or resume. It\ndoes not maintain `state`, `paused_at`, `paused_reason`, and next-run timing as\none lifecycle operation.\n\nAs with create, use typed top-level fields instead of combining them with a\n`body` wrapper. The connector removes `job_id` and transport-only fields from\nthe provider body.\n\n## Pause and resume\n\nPause an enabled job with only its ID:\n\n```json\n{\n  \"job_id\": \"a1b2c3d4e5f6\"\n}\n```\n\n`job_pause` sets `enabled` to false, state to `paused`, records `paused_at`,\nand returns the updated job. This AIP route has no pause-reason input, so the\nprovider stores no caller-supplied reason.\n\n`job_resume` clears pause metadata, enables the job, and recomputes\n`next_run_at` from the time of the request. Resuming an active job can shift\nits next run, so call it only for a record whose state you just verified as\npaused.\n\nAn expired one-shot cannot be resumed. The provider returns an error when its\nstored time is beyond the one-shot grace window. Create a replacement instead\nof retrying the same resume request.\n\n## Trigger a run\n\n`job_run` uses the same ID-only input. Despite its name, the provider does not\nexecute the job in the HTTP request. It performs this state transition:\n\n```json\n{\n  \"enabled\": true,\n  \"state\": \"scheduled\",\n  \"paused_at\": null,\n  \"paused_reason\": null,\n  \"next_run_at\": \"<provider-current-time>\"\n}\n```\n\nIt then returns `{\"job\": <updated-record>}`. The later scheduler owns model or\nscript execution, output storage, delivery, and final status.\n\nThe built-in gateway ticker normally checks every 60 seconds. A configured\nexternal scheduler can use different timing. Unlike create, update, delete,\npause, and resume, the pinned `job_run` handler does not send the best-effort\njobs-changed notification to an external scheduler provider. Verify its\nbehavior separately before promising immediate execution on that topology.\n\nTreat a successful `job_run` response as “made due,” not “ran successfully.”\nFor a recurring job, poll `job_get` until `last_run_at` advances, then inspect\n`last_status`, `last_error`, and `last_delivery_error`.\n\nA finite one-shot can be removed automatically when its run limit is reached,\nso `404` after dispatch is not proof of either success or failure. Correlate it\nwith retained output, delivery, and endpoint operational evidence. The AIP job\nfamily has no endpoint for reading saved run-output files.\n\n## Delete a job\n\n`job_delete` removes the selected record and its per-job output directory. A\nsuccessful provider response is:\n\n```json\n{\n  \"ok\": true\n}\n```\n\nDeletion cannot recall messages already delivered, reverse tool or script\neffects, remove data written elsewhere, or prove that an in-flight execution\nwas cancelled. Pause first, verify that no run is active through operational\nsignals, and then delete.\n\nThe provider resolves and validates the output path before saving the record\nremoval. A legacy unsafe ID therefore fails closed instead of partially\ndeleting scheduler state. AIP only accepts current 12-hex IDs.\n\nRecord removal and output cleanup are not one transaction. Hermes saves the\nrecord removal before deleting the output directory. If that filesystem step\nfails, the provider can return `500` after the job is already absent and leave\noutput behind. Re-list before deciding whether any cleanup remains.\n\n## Persistence and execution boundary\n\nJobs are stored under the active profile at `HERMES_HOME/cron/jobs.json`.\nWrites use a temporary file plus atomic replacement. Directories and files are\nset to owner-only permissions where the operating system supports them.\n\nMutations use an in-process lock and a best-effort cross-process advisory lock.\nIf the advisory lock cannot be opened, Hermes logs a warning and continues\nwith in-process protection. This is not a distributed database or a\ncross-instance consensus boundary.\n\nThe built-in scheduler prevents overlapping dispatch of the same job in one\nruntime and advances recurring schedules before execution. One-shots use\ndurable run claims and repeat accounting. These controls reduce duplicate\ndispatch; they do not establish exactly-once model, tool, script, or delivery\neffects across every crash and external system.\n\nEach execution writes a Markdown output file under\n`HERMES_HOME/cron/output/<job_id>`. The pinned default keeps the newest 50\nfiles per job. Output is saved before delivery. Execution failure and delivery\nfailure are recorded separately.\n\n## Protect job data and authority\n\nTreat every job record as Confidential and potentially containing PII. The\npublished AIP contract labels list/get results Internal with `contains_pii`\nfalse, but real records can disclose prompts, network origin, delivery\naddresses, errors, model routing, skills, and operational timestamps.\n\nProvider handlers authenticate the endpoint Bearer token. They do not perform\nper-job principal, creator, or tenant ownership checks. An AIP tenant binding\ncan restrict which endpoint a tenant reaches, but it does not add row-level\nauthorization inside the shared profile store.\n\nUse a separate endpoint and `HERMES_HOME` for each isolation domain, or add an\nauthorization layer that filters job IDs and list results. Never expose an\nunfiltered shared-profile job catalogue to mutually untrusted principals.\n\nRedact prompts, origins, delivery targets, provider details, and errors from\nlogs and support bundles. Store endpoint credentials outside action input.\n\n## Idempotency and retry decisions\n\nRequired mutation keys are scoped to the full endpoint-qualified capability\nID and retained by the AIP runtime for 24 hours. Reusing a key returns the\noriginal AIP result. The provider itself does not enforce the forwarded\n`Idempotency-Key` header.\n\nThe replay record does not revalidate an input hash. Reusing the same key with\na different job ID or body can return the earlier result without applying the\nnew intent. Use a new key whenever the selected job or mutation changes.\n\n| Situation | Decision |\n|---|---|\n| List/get failed before a response | Retry within the trusted deadline |\n| Mutation returned a definitive provider response | Do not retry with a new key |\n| Mutation timed out with the original AIP action unresolved | Reconcile with list/get before any new action |\n| `job_run` returned success | Poll status; do not submit another run immediately |\n| Resume rejected an expired one-shot | Create a replacement with a new key |\n| Repeat limit must change | Pause, recreate, verify, and delete; do not patch `repeat` |\n\nAn AIP timeout or cancellation can race a provider mutation that already\npersisted. Read current state before deciding whether compensation is needed.\n\n## Failures\n\n| Failure | Meaning | Operator response |\n|---|---|---|\n| Local invalid-input error | Missing or malformed 12-hex ID, query, or body | Correct input; no provider request occurred |\n| `401` or `403` | Endpoint credential absent, invalid, or denied | Repair endpoint authentication |\n| `400` | Invalid ID, field, prompt, repeat, or effective update | Correct the request; do not replay unchanged |\n| `404` | Job does not exist in this profile | Re-list including disabled jobs |\n| `501` | Hermes cron module is unavailable | Repair the endpoint installation |\n| `500` | Parsing, storage, scheduler, or provider-side handler failure | Reconcile state; inspect redacted endpoint logs |\n| AIP deadline or cancellation | Trusted execution stopped waiting | Reconcile before issuing a new mutation |\n\nSchedule parsing exceptions can surface as provider `500` in the pinned REST\nhandler rather than a more specific `400`. Read the redacted error text and\nthe submitted schedule before treating it as an infrastructure outage.\n\n## Verify an integration\n\nBefore enabling production scheduling, verify from source and controlled\nendpoint evidence that:\n\n1. Discovery exposes exactly eight job capabilities for the intended endpoint.\n2. Tenant binding and the endpoint Bearer credential fail closed.\n3. IDs are returned by create and never guessed from names.\n4. List without `include_disabled` hides a paused job; list with it shows one.\n5. Resume recalculates timing and an expired one-shot fails visibly.\n6. `job_run` returns before execution and status changes only after a tick.\n7. Execution failure and delivery failure remain distinguishable.\n8. A repeated AIP mutation key returns the original result.\n9. A changed mutation uses a new key and a fresh approval decision.\n10. Repeat limits are changed only through replacement in this pinned version.\n11. Job records and output files follow the intended profile isolation.\n12. Delete removes local record/output but is not treated as effect rollback.\n\nDo not report live qualification from this source-only checklist. Record actual\nenvironment, revision, time, and retained artifacts on the qualification page.\n\n## Related documentation\n\n- [Hermes capability index](README.md)\n- [Durable runs and approvals](durable-runs-and-approvals.md)\n- [Persistent sessions](sessions.md)\n- [Approvals and policy](../../../concepts/approvals-and-policy.md)\n- [Errors and retry decisions](../../../reference/errors.md)\n- [Authentication and endpoints](../getting-started/authentication-and-endpoints.md)\n",
    "text": "Hermes scheduled job capabilities\n\nUse these eight endpoint-qualified capabilities to manage jobs in one Hermes\nprofile's persistent scheduler.\n\nAn accepted AIP action confirms that Hermes stored or changed scheduler state.\nIt does not confirm that a job ran, produced output, or delivered a message.\nIn particular, jobrun only makes a job due for later scheduler execution.\n\nA scheduled job can invoke an agent, tools, scripts, providers, and delivery\nadapters configured outside this API. Inspect a job before approving any\noperation that creates, changes, resumes, runs, or deletes it.\n\nOperations\n\nReplace  with the normalized endpoint ID returned by AIP\ndiscovery.\n\n| Operation | Provider request | Kind | Risk | Approval | Key | Retry |\n\n| jobslist | GET /api/jobs | Tool | Low | No | Optional | Safe |\n| jobcreate | POST /api/jobs | Tool | Medium | Yes | Required | Unsafe |\n| jobget | GET /api/jobs/{jobid} | Tool | Low | No | Optional | Safe |\n| jobupdate | PATCH /api/jobs/{jobid} | Tool | Medium | Yes | Required | Unsafe |\n| jobdelete | DELETE /api/jobs/{jobid} | Tool | High | Yes | Required | Unsafe |\n| jobpause | POST /api/jobs/{jobid}/pause | Tool | Medium | Yes | Required | Unsafe |\n| jobresume | POST /api/jobs/{jobid}/resume | Tool | Medium | Yes | Required | Unsafe |\n| jobrun | POST /api/jobs/{jobid}/run | Agent | High | Yes | Required | Unsafe |\n\nAll eight capabilities are stable. They require the selected endpoint's\nBearer credential. Input cannot select a different endpoint or provider URL.\n\nEvery path operation requires a 12-character lowercase hexadecimal jobid,\nfor example a1b2c3d4e5f6. A name, uppercase ID, shortened ID, or arbitrary UUID\nis rejected by the connector before the provider call.\n\nChoose a schedule\n\nHermes parses four schedule forms.\n\n| Intent | Example | Stored kind | First eligible time |\n\n| Run once after a delay | 30m, 2h, 1d | once | Creation time plus the delay |\n| Run at an interval | every 30m, every 2h | interval | Creation time plus the interval |\n| Follow a cron expression | 0 9    | cron | Next matching occurrence |\n| Run once at a timestamp | 2030-01-15T09:00:00+04:00 | once | The supplied instant |\n\nA timestamp without an offset is interpreted in the configured Hermes\ntimezone. Supply an explicit offset when an application must preserve one\nunambiguous instant across deployments.\n\nA one-shot more than 120 seconds in the past is rejected. A one-shot created\nwithout repeat receives a run limit of one. Use recurring interval or cron\nschedules for repeated work.\n\nCron expressions depend on the provider's croniter installation. The pinned\nHermes source treats it as a runtime dependency, but an unavailable dependency\nstill makes expression parsing or next-run computation fail.\n\nShared AIP contract\n\n| Contract field | List and get | Create and update | Pause and resume | Delete | Run |\n\n| Side effects | read, externalnetwork | read, write, externalnetwork | read, write, externalnetwork | read, delete, externalnetwork | read, write, externalnetwork |\n| Published data class | Internal; no PII | Confidential; contains PII | Internal; contains PII | Restricted; contains PII | Confidential; contains PII |\n| Redaction | Required | Required | Required | Required | Required |\n| Idempotency | Optional | Required | Required | Required | Required |\n| Retry support | Yes | No | No | No | No |\n| Retry safety | Safe | Unsafe | Unsafe | Unsafe | Unsafe |\n| Completion | Sync | Sync | Sync | Sync | Sync request; delayed job |\n| Compensation | Rollback not supported | Rollback not supported | Rollback not supported | Rollback not supported | Rollback not supported |\n\nAll eight contracts declare a 60-second timeout, 5-second expected latency,\n10-second maximum queue-delay hint, and 99.9% availability target. The\nconnector applies a 30-second HTTP timeout to each JSON request. Trusted\nexecution is also bounded by the AIP deadline and cancellation token.\n\nThese values describe the control request only. They do not bound the later\njob execution. The availability value is a declaration, not observed service\nor qualification evidence.\n\nThe six mutations require tenant-policy approval. Approval lasts 15 minutes\nand records a reason, input snapshot, and policy decision. No operation\npublishes transaction support, and no compensation capability is available.\n\nThe jobrun contract understates possible downstream effects. A stored job\ncan later send a message or execute code even though the capability declares\nonly read, write, and external-network effects. Base policy on the retrieved\njob and the endpoint's Hermes configuration, not that generic list alone.\n\nList jobs\n\njobslist returns enabled jobs by default:\n\n{}\n\nInclude paused and otherwise disabled records when reconciling scheduler state:\n\n{\n  \"includedisabled\": true\n}\n\nThe provider returns one object containing a jobs array. There is no limit,\ncursor, offset, total count, or documented sort. Results retain storage order.\n\nThe connector also accepts a query object and forwards scalar values. Prefer\nthe typed top-level includedisabled field. Supplying the same key in both\nplaces creates duplicate query parameters with provider-dependent selection.\n\nThe response contains full normalized job records rather than a safe summary.\nA record can include its prompt, skills, model and provider routing, delivery\ntarget, origin network metadata, schedule, timestamps, errors, and execution\nstate. Jobs created by other Hermes surfaces can expose fields that AIP cannot\nset.\n\nThe list is profile-scoped, not caller-scoped. It covers every job in the\nselected endpoint's active HERMESHOME, including jobs created outside AIP.\n\nCreate a job\n\nUse top-level fields for the frozen AIP schema:\n\n{\n  \"name\": \"Reconcile support queue\",\n  \"schedule\": \"every 30m\",\n  \"prompt\": \"Summarize unresolved queue items and record the totals.\",\n  \"deliver\": \"local\",\n  \"skills\": [\"support-reporting\"],\n  \"repeat\": 24\n}\n\n| Field | Requirement | Frozen AIP bound |\n\n| name | Required, non-empty string | 200 characters |\n| schedule | Required, non-empty string | 512 characters |\n| prompt | Optional string | 5,000 characters |\n| deliver | Optional string | 256 characters |\n| skills | Optional string array | 256 items, 256 characters each |\n| repeat | Optional positive integer | At least one |\n\nThe schema rejects other top-level fields. Although a body property exists\nfor transport compatibility, mixing it with typed fields can cause the wrapper\nto replace those fields before the provider call. Use the typed form above.\n\nThe provider trims name and schedule, parses the schedule, and generates a\n12-hex job ID. When the optional prompt scanner imports successfully, it also\nrejects blocked patterns in a non-empty prompt. An API-created job records\napiserver origin metadata and defaults delivery to local.\n\nHermes stores the initial job as enabled with state scheduled. It calculates\nnextrunat, initializes run counts and status, and returns:\n\n{\n  \"job\": {\n    \"id\": \"a1b2c3d4e5f6\",\n    \"name\": \"Reconcile support queue\",\n    \"schedule\": {\n      \"kind\": \"interval\",\n      \"minutes\": 30,\n      \"display\": \"every 30m\"\n    },\n    \"scheduledisplay\": \"every 30m\",\n    \"repeat\": {\n      \"times\": 24,\n      \"completed\": 0\n    },\n    \"enabled\": true,\n    \"state\": \"scheduled\",\n    \"lastrunat\": null,\n    \"laststatus\": null,\n    \"lasterror\": null,\n    \"lastdeliveryerror\": null,\n    \"deliver\": \"local\"\n  }\n}\n\nThe actual response contains more fields. Treat the returned ID as the only\nvalid identifier for later AIP operations.\n\nPut the required idempotency key on the AIP Action, not in this JSON. The\nconnector forwards it as Idempotency-Key; the pinned provider handler does\nnot interpret that header.\n\nInspect one job\n\nUse jobget before every governed mutation:\n\n{\n  \"jobid\": \"a1b2c3d4e5f6\"\n}\n\nThe provider returns {\"job\": } or 404 when the ID is absent.\nInspect at least these fields:\n• enabled, state, nextrunat, and schedule for eligibility;\n• prompt, skills, and any script or toolset fields for execution scope;\n• provider, model, and delivery fields for external destinations;\n• repeat, lastrunat, laststatus, and errors for prior outcomes.\n\nAn origin record is audit context. It is not proof that the current caller\ncreated or owns the job.\n\nUpdate safe fields\n\njobupdate accepts a path ID and a partial body:\n\n{\n  \"jobid\": \"a1b2c3d4e5f6\",\n  \"name\": \"Reconcile priority support queue\",\n  \"schedule\": \"every 1h\",\n  \"prompt\": \"Summarize unresolved priority items and record the totals.\"\n}\n\nThe provider whitelists name, schedule, prompt, deliver, skills,\nlegacy skill, repeat, and enabled. Other provider fields are discarded.\nAn effective patch with no whitelisted field returns 400.\n\nA changed schedule is reparsed. If the job is not paused, Hermes recalculates\nits next eligible time. A non-empty changed prompt is scanned again.\n\nDo not patch repeat in the pinned version. The REST handler forwards an\ninteger, but the scheduler later expects an object with times and\ncompleted. This source-level shape mismatch can leave the job unreadable to\nexecution paths. To change the limit:\n1. Pause and inspect the old job.\n2. Create a replacement with the intended schedule and repeat value.\n3. Inspect the replacement and then delete the old job.\n\nDo not use a direct enabled patch as a substitute for pause or resume. It\ndoes not maintain state, pausedat, pausedreason, and next-run timing as\none lifecycle operation.\n\nAs with create, use typed top-level fields instead of combining them with a\nbody wrapper. The connector removes jobid and transport-only fields from\nthe provider body.\n\nPause and resume\n\nPause an enabled job with only its ID:\n\n{\n  \"jobid\": \"a1b2c3d4e5f6\"\n}\n\njobpause sets enabled to false, state to paused, records pausedat,\nand returns the updated job. This AIP route has no pause-reason input, so the\nprovider stores no caller-supplied reason.\n\njobresume clears pause metadata, enables the job, and recomputes\nnextrunat from the time of the request. Resuming an active job can shift\nits next run, so call it only for a record whose state you just verified as\npaused.\n\nAn expired one-shot cannot be resumed. The provider returns an error when its\nstored time is beyond the one-shot grace window. Create a replacement instead\nof retrying the same resume request.\n\nTrigger a run\n\njobrun uses the same ID-only input. Despite its name, the provider does not\nexecute the job in the HTTP request. It performs this state transition:\n\n{\n  \"enabled\": true,\n  \"state\": \"scheduled\",\n  \"pausedat\": null,\n  \"pausedreason\": null,\n  \"nextrunat\": \"\"\n}\n\nIt then returns {\"job\": }. The later scheduler owns model or\nscript execution, output storage, delivery, and final status.\n\nThe built-in gateway ticker normally checks every 60 seconds. A configured\nexternal scheduler can use different timing. Unlike create, update, delete,\npause, and resume, the pinned jobrun handler does not send the best-effort\njobs-changed notification to an external scheduler provider. Verify its\nbehavior separately before promising immediate execution on that topology.\n\nTreat a successful jobrun response as “made due,” not “ran successfully.”\nFor a recurring job, poll jobget until lastrunat advances, then inspect\nlaststatus, lasterror, and lastdeliveryerror.\n\nA finite one-shot can be removed automatically when its run limit is reached,\nso 404 after dispatch is not proof of either success or failure. Correlate it\nwith retained output, delivery, and endpoint operational evidence. The AIP job\nfamily has no endpoint for reading saved run-output files.\n\nDelete a job\n\njobdelete removes the selected record and its per-job output directory. A\nsuccessful provider response is:\n\n{\n  \"ok\": true\n}\n\nDeletion cannot recall messages already delivered, reverse tool or script\neffects, remove data written elsewhere, or prove that an in-flight execution\nwas cancelled. Pause first, verify that no run is active through operational\nsignals, and then delete.\n\nThe provider resolves and validates the output path before saving the record\nremoval. A legacy unsafe ID therefore fails closed instead of partially\ndeleting scheduler state. AIP only accepts current 12-hex IDs.\n\nRecord removal and output cleanup are not one transaction. Hermes saves the\nrecord removal before deleting the output directory. If that filesystem step\nfails, the provider can return 500 after the job is already absent and leave\noutput behind. Re-list before deciding whether any cleanup remains.\n\nPersistence and execution boundary\n\nJobs are stored under the active profile at HERMESHOME/cron/jobs.json.\nWrites use a temporary file plus atomic replacement. Directories and files are\nset to owner-only permissions where the operating system supports them.\n\nMutations use an in-process lock and a best-effort cross-process advisory lock.\nIf the advisory lock cannot be opened, Hermes logs a warning and continues\nwith in-process protection. This is not a distributed database or a\ncross-instance consensus boundary.\n\nThe built-in scheduler prevents overlapping dispatch of the same job in one\nruntime and advances recurring schedules before execution. One-shots use\ndurable run claims and repeat accounting. These controls reduce duplicate\ndispatch; they do not establish exactly-once model, tool, script, or delivery\neffects across every crash and external system.\n\nEach execution writes a Markdown output file under\nHERMESHOME/cron/output/. The pinned default keeps the newest 50\nfiles per job. Output is saved before delivery. Execution failure and delivery\nfailure are recorded separately.\n\nProtect job data and authority\n\nTreat every job record as Confidential and potentially containing PII. The\npublished AIP contract labels list/get results Internal with containspii\nfalse, but real records can disclose prompts, network origin, delivery\naddresses, errors, model routing, skills, and operational timestamps.\n\nProvider handlers authenticate the endpoint Bearer token. They do not perform\nper-job principal, creator, or tenant ownership checks. An AIP tenant binding\ncan restrict which endpoint a tenant reaches, but it does not add row-level\nauthorization inside the shared profile store.\n\nUse a separate endpoint and HERMESHOME for each isolation domain, or add an\nauthorization layer that filters job IDs and list results. Never expose an\nunfiltered shared-profile job catalogue to mutually untrusted principals.\n\nRedact prompts, origins, delivery targets, provider details, and errors from\nlogs and support bundles. Store endpoint credentials outside action input.\n\nIdempotency and retry decisions\n\nRequired mutation keys are scoped to the full endpoint-qualified capability\nID and retained by the AIP runtime for 24 hours. Reusing a key returns the\noriginal AIP result. The provider itself does not enforce the forwarded\nIdempotency-Key header.\n\nThe replay record does not revalidate an input hash. Reusing the same key with\na different job ID or body can return the earlier result without applying the\nnew intent. Use a new key whenever the selected job or mutation changes.\n\n| Situation | Decision |\n\n| List/get failed before a response | Retry within the trusted deadline |\n| Mutation returned a definitive provider response | Do not retry with a new key |\n| Mutation timed out with the original AIP action unresolved | Reconcile with list/get before any new action |\n| jobrun returned success | Poll status; do not submit another run immediately |\n| Resume rejected an expired one-shot | Create a replacement with a new key |\n| Repeat limit must change | Pause, recreate, verify, and delete; do not patch repeat |\n\nAn AIP timeout or cancellation can race a provider mutation that already\npersisted. Read current state before deciding whether compensation is needed.\n\nFailures\n\n| Failure | Meaning | Operator response |\n\n| Local invalid-input error | Missing or malformed 12-hex ID, query, or body | Correct input; no provider request occurred |\n| 401 or 403 | Endpoint credential absent, invalid, or denied | Repair endpoint authentication |\n| 400 | Invalid ID, field, prompt, repeat, or effective update | Correct the request; do not replay unchanged |\n| 404 | Job does not exist in this profile | Re-list including disabled jobs |\n| 501 | Hermes cron module is unavailable | Repair the endpoint installation |\n| 500 | Parsing, storage, scheduler, or provider-side handler failure | Reconcile state; inspect redacted endpoint logs |\n| AIP deadline or cancellation | Trusted execution stopped waiting | Reconcile before issuing a new mutation |\n\nSchedule parsing exceptions can surface as provider 500 in the pinned REST\nhandler rather than a more specific 400. Read the redacted error text and\nthe submitted schedule before treating it as an infrastructure outage.\n\nVerify an integration\n\nBefore enabling production scheduling, verify from source and controlled\nendpoint evidence that:\n1. Discovery exposes exactly eight job capabilities for the intended endpoint.\n2. Tenant binding and the endpoint Bearer credential fail closed.\n3. IDs are returned by create and never guessed from names.\n4. List without includedisabled hides a paused job; list with it shows one.\n5. Resume recalculates timing and an expired one-shot fails visibly.\n6. jobrun returns before execution and status changes only after a tick.\n7. Execution failure and delivery failure remain distinguishable.\n8. A repeated AIP mutation key returns the original result.\n9. A changed mutation uses a new key and a fresh approval decision.\n10. Repeat limits are changed only through replacement in this pinned version.\n11. Job records and output files follow the intended profile isolation.\n12. Delete removes local record/output but is not treated as effect rollback.\n\nDo not report live qualification from this source-only checklist. Record actual\nenvironment, revision, time, and retained artifacts on the qualification page.\n\nRelated documentation\n• Hermes capability index (README.md)\n• Durable runs and approvals (durable-runs-and-approvals.md)\n• Persistent sessions (sessions.md)\n• Approvals and policy (../../../concepts/approvals-and-policy.md)\n• Errors and retry decisions (../../../reference/errors.md)\n• Authentication and endpoints (../getting-started/authentication-and-endpoints.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "f24f41373fb22a814a0213781b4d7b744701fd7cfec00ca34b978370925b7ef8"
  }
}
