Create Your First Automation

Create a repeating perimeter check, then add an event-triggered alarm and a scheduled task that calls a specialist agent.

You should have completed Getting Started and Write Your First Skill first.

What is an automation?

An automation is a .lua file under /sdcard/atak/custos/automations/ with comment annotations describing when to fire and a top-level function run(ctx) that executes when the trigger fires. There are three trigger types:

Tab Trigger shape Examples
Scheduled cron, one-shot 0 6 * * *, in 20m
Monitors duration 15s, 30m, 2h
Events broadcast action com.atakmap.android.maps.COT_RECD

run(ctx) executes Lua and invokes other skills through tools.call. The first examples run without a model call. The scheduled example explicitly calls tools.call("delegate", {agent_name = "...", task = "..."}) to request model reasoning.

Step 1 — Ask the agent to write an automation

The custos.automation skill has one tool — write_automation — and the agent picks the trigger type based on the expression you describe.

In the chat panel:

Create an automation called perimeter_monitor that every 15 seconds checks
for hostile contacts within 300m of my position and plays an alarm if any
are found.

The agent writes /sdcard/atak/custos/automations/perimeter_monitor.lua:

--- 300m hostile perimeter sweep
-- @automation perimeter_monitor
-- @trigger 15s
-- @session ISOLATED

function run(interval)
    local pos = tools.call("get_self_position", {})
    local result = tools.call("find_nearby", {
        lat = pos.lat, lon = pos.lon, radius_m = 300, affiliation = "hostile",
    })
    if result.count > 0 then
        tools.call("play_tone", { type = "alarm" })
    end
end

The run(interval) parameter name matches the trigger type. When the 15s interval fires, this Lua code runs in a sandbox, calls three skills in sequence, and exits. No LLM involved.

Step 2 — Open the Automation panel

Tap the Automation icon in the NavBar. Three tabs:

  • Scheduled — cron and one-shot triggers
  • Monitors — duration-based intervals
  • Events — broadcast-driven

Your perimeter_monitor is on the Monitors tab. Each row shows the next fire time, enable/disable toggle, edit, and delete.

Step 3 — Build an event-driven alarm

Ask:

Create an event automation called hostile_alert that fires when ATAK receives
a CoT event of type a-h-* and speaks the callsign via TTS.

The agent writes:

--- Alert on hostile CoT
-- @automation hostile_alert
-- @trigger com.atakmap.android.maps.COT_RECD
-- @session ISOLATED

function run(event)
    if not (event.cot_type and event.cot_type:find("a-h-")) then return end
    tools.call("speak_alert", { message = "Hostile: " .. (event.callsign or "unknown") })
end

Things to notice:

  • The @trigger is the broadcast intent action string.
  • event is a table of parsed broadcast fields — cot_type, uid, lat, lon, callsign, etc.
  • Filtering is a guard clause at the top of run() — no separate annotation.
  • @session ISOLATED keeps automation output out of the operator's active chat.

Step 4 — An LLM-backed schedule

Use delegate to request model reasoning. It requires a named agent in custos.yaml. This step assumes you have configured an agent called analyst with a chat provider. See the agent block in the configuration reference.

With an analyst agent configured, ask:

Create a morning_sitrep automation that runs at 0600 daily, asks the analyst
agent to produce a SITREP, and speaks it aloud.

The result:

--- Daily 0600 SITREP
-- @automation morning_sitrep
-- @trigger 0 6 * * *
-- @session MAIN

function run(schedule)
    local result = tools.call("delegate", {
        agent_name = "analyst",
        task = "Generate a comprehensive SITREP from the current tactical picture.",
    })
    tools.call("speak_alert", { message = result.response })
end

Here, run() calls delegate and passes the returned response to the next tool. The agent_name must match an agent's name field in custos.yaml exactly.

Step 5 — Edit, disable, delete

From the Automation panel:

  • Toggle — flips enabled/disabled.
  • Edit — opens the file in the in-app editor.
  • Delete — removes the file.

What you learned

  • Automations are .lua files with @automation, @trigger, @session, @description annotations and a run(ctx) function.
  • Trigger type is auto-detected — no @type annotation.
  • run(ctx) executes deterministically; LLM access is only through explicit tools.call("delegate", ...).
  • Filter logic is a guard clause inside run(); no @filter annotation.
  • The agent writes automations for you via write_automation.

Where to go next