# Background tasks (M1)

Declare `jobs` and require `et.jobs.v1`, then query `ET_SERVICE_JOBS` version
`ET_SERVICE_VERSION_1`. This optional 32-byte table uses ABI 1.1 discovery.
Existing 1.0 plugins remain compatible. The Qt-free C++ wrapper is
`<epictuner/sdk/jobs.hpp>`; `background.cpp` shows the C callbacks and result data.

Call submit/cancel on the worker dispatcher. Work runs on a background thread.
At most 16 jobs are outstanding; excess submission returns ET_ERROR_LIMIT without
blocking. Each job has a 4096-byte result mailbox, published with atomic
release/acquire synchronization. Only the cancellation probe is callable from
work; other services, including logging, require the dispatcher. Return data
and log it in completion. No native pointer or callback travels over IPC.

The plugin owns work_context and options.context through completion. Submit copies
options, issues a worker-global job handle, and never calls completion inline.
Work writes into the supplied buffer, sets size and returns an et_result; do not
change its pointer/capacity. Completion receives borrowed ET_VALUE_BYTES on success
or a null value on error. Its operation ID equals the job handle slot. Copy retained
bytes before returning. Author callbacks must contain exceptions for cross-compiler
use; the worker also catches compatible unexpected exceptions as containment.

Cancellation is cooperative. Cancel signals the atomic probe; timeout does the
same after timeout_ms (1–3000 ms). The first cancellation reason wins. Completion
runs exactly once AFTER work returns, so the plugin can safely release contexts.
Work ignoring cancellation for another 1000 ms fails and terminates the worker;
callbacks cannot be promised after process failure. The broker also enforces its
independent shutdown deadline. Stop rejects new jobs, cancels/drains outstanding
work and completions, THEN invokes plugin.stop and destroys the instance.
Completion callbacks must return promptly, like other dispatcher callbacks.

Completed, forged, wrong-kind/session and project-scoped job handles return
ET_ERROR_STALE. Slots never reuse within a worker; new random sessions invalidate
prior handles. Results and work never replay on restart. Jobs are worker-global
CPU work; project services, progress UI and ECU work belong to later milestones.

Build all three examples with examples/lifecycle/CMakeLists.txt. Run:

```
epictuner-plugin-check --development --manifest out/plugin-background.json
```

Expected log: background sum=55, then stop after completion. In EpicTuner, launch
with --plugin-development and use Tools → Development Plugins → Load development
manifest. --safe-mode overrides development mode and refuses all starts.
Development enablement/manifests are per-launch; unsigned plugins never auto-load
from previous sessions. Signed installation, trust, persistent enablement and
update/rollback belong to M5.
