Engagement
The planner proposes; a person disposes. This surface is where that happens: read the threats, set the autonomy gate, and approve or reject each candidate plan by name.
Threats
GET /api/threats returns the current engagement state: assessed threats, the
active plan, and the candidates awaiting a decision. An operator's judgement enters
through designation:
curl -X POST https://<your-backend-host>/api/threats/designate \
-H "Content-Type: application/json" \
-d '{ "id": "T-00492", "verdict": "hostile", "by": "op-1" }'
verdict is hostile (this IS a threat: onto the board, engage-eligible under the
authority gate) or benign (this is not: resolved off the board, and it sticks,
scoped to this one object).
The response carries attribution_verified: false: on this open surface the by
field is a claim, recorded but not proven. GET /api/threats/designations lists
the designations in force.
The autonomy gate
curl https://<your-backend-host>/api/engagement/mode
curl -X PUT https://<your-backend-host>/api/engagement/mode \
-H "Content-Type: application/json" -d '{ "mode": "semi_auto" }'
| Mode | Meaning |
|---|---|
manual | Everything becomes a proposal, including reflexive actions. |
semi_auto | Reflexive actions auto-execute against a confirmed hostile; proposed and kinetic actions need a human. The default. |
auto | Reflexive and proposed actions auto-execute within the rules of engagement; kinetic still needs a human. |
full_auto | Weapons free: everything auto-executes, including kinetic. The console flags this mode prominently. |
The mode is the operator's authority ceiling. Rules-of-engagement gating and a
withheld posture still bind in every mode, full_auto included.
Candidate plans
The decision loop publishes candidate plans; each waits for a verdict:
curl https://<your-backend-host>/api/engagement/plans
curl -X POST https://<your-backend-host>/api/engagement/plans/P-3/approve
curl -X POST https://<your-backend-host>/api/engagement/plans/P-3/reject
Approval flips the plan's assignments to approved, and the order transport delivers each one to whatever holds the asset, real or simulated. Two refusals matter:
| Status | When |
|---|---|
404 | The plan id is not in the current candidate set. Candidates are superseded on every replan, so a stale id is expected client behaviour to handle. |
409 | Posture withholds the plan. The error carries the posture's reason. |
Watch the engagement_slice and plan events on the stream
rather than polling GET /api/engagement/plans; candidates change on the planner's
clock, not yours. POST /api/engagement/plan/run asks the planner for a fresh pass
(optionally { "variation_count": 3 }).
Task from the map
Point and area tasking pick the nearest free friendly asset and issue the matching verb through the same command path as everything else:
curl -X POST https://<your-backend-host>/api/engagement/task/point \
-H "Content-Type: application/json" \
-d '{ "lat": 1.3521, "lon": 103.8198 }'
curl -X POST https://<your-backend-host>/api/engagement/task/area \
-H "Content-Type: application/json" \
-d '{ "lat": 1.3521, "lon": 103.8198, "radius_m": 400 }'
{ "ok": true, "data": { "asset": "mav-udp0-3", "verb": "move_to" } }
A point becomes move_to; an area becomes surveil, which is ROE-gated like any
tasking verb. 400 when no free asset exists to take the task.
Missions and stored plans
/api/missions and /api/plans hold the persisted planning surface: propose
candidates (POST /api/plans/propose), store one, and POST /api/plans/{id}/approve
turns it into a mission. This is distinct from /api/engagement/plans, which is the
live runtime's candidate set. The two meet at execution: an approved plan's orders go
through the one command path described in
Assets & commands.