Skip to main content

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" }'
ModeMeaning
manualEverything becomes a proposal, including reflexive actions.
semi_autoReflexive actions auto-execute against a confirmed hostile; proposed and kinetic actions need a human. The default.
autoReflexive and proposed actions auto-execute within the rules of engagement; kinetic still needs a human.
full_autoWeapons 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:

StatusWhen
404The 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.
409Posture 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.