Skip to content
Agentmart
Submit a tool

automation

Create recurring agent tasks — scheduled checks, perimeter monitors, and event-driven watchers that run automatically

Skill by nimbusxr

IndexedMIT★ 7
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/nimbusxr-automation

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Create recurring agent tasks — scheduled checks, perimeter monitors, and event-driven watchers that run automatically

Use cases: agent-building

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/automation

What's inside

Skill name
automation
Description
Create recurring agent tasks — scheduled checks, perimeter monitors, and event-driven watchers that run automatically
License
MIT
Instructions
110 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • name: "automation" does not match folder "custos.automation"

Files (3)

  • SKILL.md
  • manage_automation.lua
  • write_automation.lua

SKILL.md (first lines)

You create and manage automations for the CUST/OS operator. An automation is a `.lua` file with a top-level `function run(ctx)` that the scheduler invokes when the trigger fires. Automations execute as deterministic Lua — the LLM is not touched automatically.

## Core rule: automations never call the LLM implicitly

Every automation is just Lua. If the task needs LLM reasoning, the `run()` body MUST explicitly call `tools.call("delegate", {agent_name = "...", task = "..."})`. Anything else — checking state, placing markers, playing alerts, sending messages — goes through `tools.call("skill_name", {...})` directly and runs without the LLM.

## How `tools.call` works

Inside `run()`, invoke any registered tool by its **tool name** (NOT its skill ID):

```lua
local pos = tools.call("get_self_position", {})
local hostiles = tools.call("find_nearby", { lat = pos.lat, lon = pos.lon, radius_m = 300, affiliation = "hostile" })
tools.call("play_tone", { type = "alarm" })
```

Arguments are a Lua table; return value is a Lua table with the tool's result.

### Tool names ≠ skill IDs

This trips up every LLM. They are different things:

- **Skill IDs** are dotted namespaces like `custos.tactical_picture`, `custos.markers`, `custos.fires`. They group related tools.
- **Tool names** are short identifiers like `get_picture`, `place_marker`, `hostile_list`. They're the `@tool` annotation in a `.lua` file and the thing you pass to `tools.call`.

You do NOT call `tools.call("custos.tactical_picture", ...)`. You call `tools.call("get_picture", ...)`.

Common hallucination to avoid: inventing a tool name by combining words from the skill ID. The skill `custos.tactical_picture` does **not** expose a tool called `get_tactical_picture` — it exposes `get_picture`. The skill `custos.fires` does **not** expose `get_fires` — it exposes `hostile_list` and `danger_close_check`.

### Before writing an automation: verify tool names

If you're not 100% sure a tool exists by the name you're about to use, call `discover_api(query="<topic>")` from `custos.skill_creator` first to enumerate the real tool names. `write_automation` validates every `tools.call(...)` reference in the body before writing — unknown tool names are rejected with an error listing them, so hallucinated names will fail at write time, not at runtime.

## The `run(ctx)` signature

The parameter name matches the trigger type (self-documenting convention — Lua doesn't enforce names):

- **Schedule (cron or one-shot)**: `function run(schedule)` → `ctx = { fired_at, cron }`
- **Interval (duration)**: `function run(interval)` → `ctx = { fired_at, interval }`
- **Event (broadcast)**: `function run(event)` → `ctx = { fired_at, action, cot_type, uid, lat, lon, callsign, hae, how, cot_xml, extra_* }`

`fired_at` is milliseconds since epoch. For event triggers, additional fields come from the parsed broadcast intent (CoT XML, extras, etc.).

## Examples

**Interval — perimeter monitor:**
```
write_automation(
    name = "perimeter_monitor",
    trigger = "15s",
    body = [[
        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
    ]],
    description = "300m hostile perimeter sweep every 15 seconds",
    session = "ISOLATED"
)
```

**Schedule — morning SITREP with LLM reasoning via delegate:**
```
write_automation(
    name = "morning_sitrep",
    trigger = "0 6 * * *",
    body = [[
        local result = tools.call("delegate", {
            agent_name = "orchestrator",
            task = "Generate a comprehensive SITREP from the current tactical picture."
        })
        tools.call("speak_alert", { message = result.response })
    ]],
    description = "Daily 0600 SITREP",
    session = "MAIN"
)
```

**Event — hostile CoT alert with inline filter:**
```
write_automation(
    name = "hostile_alert",
    trigger = "com.atakmap.android.maps.COT_RECD",
    body = [[
        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") })
    ]],
    description = "Fire when a hostile CoT is received",
    session = "ISOLATED"
)
```

## Filter and debounce patterns

- **Filter** — put a guard clause at the top of `run()`: `if not (event.cot_type and event.cot_type:find("a-h-")) then return end`. No separate `@filter` annotation.
- **Debounce** — if truly needed, track `last_fired_at` in the memory service: `local last = memory:get("myauto_last") or 0; if fired_at - last < 60000 then return end; memory:put("myauto_last", fired_at)`. Rarely needed — deterministic Lua is cheap.

## Managing automations

Use `manage_automation` to list, toggle, or delete existing automations.

## Critical rules

- Always use `write_automation` to create automations — never write raw `.lua` files manually.
- Prefer deterministic `tools.call` chains over `delegate`. Only delegate when the task actually requires reasoning over variable inputs.
- For event triggers, use `discover_api(query="intents")` to verify the broadcast action exists before creating.
- Use `session = "ISOLATED"` (the default) for background automations so they don't pollute the operator's active conversation.

From nimbusxr/custos, MIT.

More from nimbusxr/custos

Signals

Stars
7
Forks
0
Open issues
1
Last commit (this folder)
Apr 21, 2026 (6 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from nimbusxr

Cite this page

automation skill. Agent Mart. https://agentmart.dev/skills/nimbusxr-automation (last updated 2026-10-24)