Skip to content

Latest commit

 

History

History
621 lines (494 loc) · 35 KB

SKILL_EXECUTORS.md

File metadata and controls

621 lines (494 loc) · 35 KB

Skill Executors — System Extension Reference

Status: active

The system extension ships 67 skill executors at extensions/system/server/app/services/system/ai/skills/ (the directory holds 68 *_executor.rb files; one is the abstract BaseSkillExecutor, and all 67 concrete executors declare binds_to). Each pairs with an Ai::Skill record (seeded by system_skills_seed.rb) that makes it discoverable via platform.discover_skills. Skills are bound to autonomy + chat agents via Ai::AgentSkill.

For the full 67-executor list with descriptor I/O, see SKILL_EXECUTOR_CATALOG.md — auto-generated by cd server && bundle exec rails system:skills:generate_catalog (run from the parent platform server/ tree; the extension has no server/lib/tasks). That catalog is the canonical enumeration (re-run the rake task rather than trusting counts restated in prose) and groups executors under the 8 descriptor() categories — Devops, Documentation, Federation, Fleet, Governance, Sdwan, Security, System. The per-category tallies that used to be restated here were stale by more than one release (they summed to 54 against a 57-executor list) and are deliberately NOT repeated: read them off the generated catalog, which is the only copy a rake task keeps true. It covers every executor: CVE remediation orchestration, full-stack provisioning, package management, SDWAN OVN/host-bridge/IPFIX/full-topology composition, federation_manager, the architecture_* catalog skills, workload relocation, and platform_deploy/platform_maintenance/platform_resilience.

This document focuses on architecture — agent bindings, plan-vs-execute pattern, invocation surfaces — and covers 14 representative executors in depth as worked examples. New executors should ship a descriptor() method (so they appear in the auto-generated catalog) and optionally extend this doc when their orchestration pattern adds something new.

Two category taxonomies (don't confuse them): an executor's descriptor()[:category] is one of the 8 executor categories above (used to organize the catalog). The seed in system_skills_seed.rb maps each executor onto a platform Ai::Skill enum value (devops, security, sre_observability, release_management, documentation) for cross-platform skill discovery. The two taxonomies are independent — the per-executor reference below cites the Ai::Skill enum value the seed assigns.

Agent → Skill Bindings (representative)

The 2026-05-10 agent split moved CVE work out of Fleet Autonomy into a dedicated CVE Responder agent; Disk Image Manager and System Topology Designer followed, and the GitOps Reconciler plus the four HIER-P2 operations managers (Capacity, Storage, Ingress, Supply Chain) brought the system extension to 12 seed agents (see the extension's CLAUDE.md for the full roster). The binding map below is representative — Topology Designer's 8 topology skills (the 5 SDWAN composition skills plus the three composer gates it took at HIER-P2F) and the federation/platform-deploy skills are enumerated in SKILL_EXECUTOR_CATALOG.md:

Skill Bound To Why
system-capacity-recommend System Concierge Read-shape — operator chat ("do I need more nodes?")
system-attribute-failure System Concierge Read-shape — diagnostic chat ("why did instance X fail?")
system-runbook-generate System Concierge Read-shape — generates docs
system-cve-runbook-generate System Concierge + CVE Responder Read-shape — generates CVE remediation runbooks
system-drift-remediate Fleet Autonomy Autonomous reconciliation
system-sdwan-failover Fleet Autonomy Autonomous SDWAN remediation
system-sdwan-peer-remediate Fleet Autonomy Autonomous SDWAN remediation
system-sdwan-bgp-session-remediate Fleet Autonomy Autonomous SDWAN remediation
system-sdwan-vip-failover Fleet Autonomy Autonomous SDWAN remediation
system-module-compose Fleet Autonomy Autonomous module planning
system-rolling-module-upgrade Fleet Autonomy + CVE Responder Autonomous release planning (CVE-driven via CVE Responder)
system-package-repository-sync Supply Chain Manager Package catalog sync (system.package_repository.sync, auto_approve); re-bound from Fleet Autonomy by HIER-P2E — the sensor path gates the same category under this agent
system-package-module-create Supply Chain Manager + System Concierge Package-derived module creation (system.package_module.create, require_approval — every new package is operator-audited); re-bound from Fleet Autonomy by HIER-P2E
system-package-module-refresh Supply Chain Manager + System Concierge + CVE Responder Package drift refresh; nothing sensor-routed reaches it — the CVE Responder's remediation orchestration invokes it directly under the CVE lane's gate, so that binding stays (HIER-P2E)
system-architecture-propose / -create / -update / -delete Supply Chain Manager Architecture catalog (system.architecture.*); re-bound from Fleet Autonomy by HIER-P2E
system-suggest-architectures-for-fleet Supply Chain Manager + System Concierge Read-shape — ranks a repo's architectures by fleet coverage; re-bound from Fleet Autonomy by HIER-P2E
system-discover-packages-by-intent Fleet Autonomy + System Concierge Intent-based semantic package discovery — operator chat ("find me a web server") + autonomous module composition
system-cve-response CVE Responder CVE triage (moved from Fleet Autonomy 2026-05-11)
system-cve-remediation-orchestration CVE Responder Chains triage → refresh → rolling upgrade for inline notify_and_proceed dispatch
system-provision-cluster Runtime Manager Container runtime lifecycle (Phase 2 K3s)
system-restore-volume Storage Manager Approval-gated volume restore (system.restore_volume); re-bound from Fleet Autonomy by HIER-P2C so the gate resolves the owning agent's row
system-docker-provision Runtime Manager Container runtime lifecycle (Phase 1 Docker)
system-disk-image-promote Disk Image Manager Promote a published publication to the platform default (system.disk_image_publication_promote, require_approval); thin over Executors::DiskImage::PromotePublication — HIER-P2F
system-disk-image-rollback Disk Image Manager Roll a platform back to a prior publication (system.disk_image_publication_rollback, require_approval); same target selection as system_revert_disk_image — HIER-P2F
system-disk-image-retention Disk Image Manager Set a platform's retention count (system.disk_image_retention_update, auto_approve — the row decides, not the descriptor) — HIER-P2F
system-gitops-sync-repository GitOps Reconciler Sync one repository now — mints the run, hands it to Gitops::Reconciler, applies nothing (system.gitops_sync_repository, auto_approve) — HIER-P2F
system-gitops-apply-proposal GitOps Reconciler Apply an approved drift proposal via Gitops::ApplyService (system.gitops_apply_proposal, require_approval — the SAME control the system_gitops_apply_proposal MCP verb parks under) — HIER-P2F
system-gitops-register-repository GitOps Reconciler Register a declarative source of fleet state (system.gitops_register_repository, require_approval) — HIER-P2F
system-sdwan-federation-compose / system-multi-tenant-isolation / system-service-discovery-composer System Topology Designer The three composer gates in TOPOLOGY_DESIGNER_POLICIES (all require_approval, chain Topology Designer Actions); bound via binds_to "topology_designer" since HIER-P2F
system-expose-service-local Ingress Manager + System Concierge Approval-gated /svc/<slug> publish (system.expose_service_local); re-bound from System Concierge alone by HIER-P2D — the Concierge keeps the operator-chat door, the gate resolves the executing agent's row
system-expose-service-publicly Ingress Manager + System Concierge Approval-gated public HTTPS exposure (system.expose_service_publicly, VIP → port map → ACME → Traefik); HIER-P2D
system-expose-service-public-tcp Ingress Manager + System Concierge Approval-gated public TLS-carrying TCP exposure ON (system.expose_service_public_tcp); the OFF arm is the ungated system_unexpose_service_public_tcp MCP action on the same executor; HIER-P2D
system-acme-certificate-provision Ingress Manager + System Concierge Approval-gated DNS-01 issuance (system.acme_certificate_provision); Fleet Autonomy dropped by HIER-P2D — no sensor routes to this executor, the system.acme_cert_rotate renewal lane fires platform_maintenance and stays Fleet Autonomy's
system-replace-instance Capacity Manager Disaster-recovery replace of an unrecoverable instance (system.instance_replace, require_approval; the instance_unrecoverable_sensor lane gates under this agent). Re-bound from Fleet Autonomy by HIER-P2B
system-reap-instance Capacity Manager The destructive half of a replace, asked as a second approval (system.instance_reap). Re-bound from Fleet Autonomy by HIER-P2B
system-relocate-workload Capacity Manager Cross-region workload relocation (system.relocate_workload, require_approval). Re-bound from Fleet Autonomy by HIER-P2B
system-scale-project Capacity Manager Provisioning-project footprint changes (add/remove replicas, add region, vertical resize plan); dispatched by System::AdaptationGate under project.scale_horizontal. Re-bound from Fleet Autonomy by HIER-P2B
system-provision-full-stack Capacity Manager Compute + storage + SDWAN stack from a template — the composer scale_project / relocate_workload call. Re-bound from Fleet Autonomy by HIER-P2B
system-governance-gap-propose Platform Architect HIER-P3 — the first executor bound to a CORE canonical (binds_to "platform_architect"): files or updates the one improvement offer for a system.governance_gap signal and materialises the runtime-closable kinds through System::Governance::GapMaterializer (dev.campaign_propose, auto_approve — the offer is the gate; a skill binding / prompt refinement applies from the trusted tier under dev.skill_refine / dev.prompt_refine where that trust-conditioned pair is seeded — the Powernode Admin account — and at EVERY tier elsewhere, through core's account-wide floor for those two categories (IMP-a51963f8717f), an edge / delegation policy parks under dev.governance_materialize)
system-attach-storage Capacity Manager Provision + attach + mount a cloud volume on a running instance — the provisioning mission's adaptive-evolution step (Ai::Provisioning::AdaptationProposerService, provisioning subdomain). Re-bound from Fleet Autonomy by HIER-P2SWEEP (driver ruling): it runs DURING PROVISIONING, so it belongs to the provisioning-step owner; the Storage Manager owns the volume DATA plane (restore, snapshot delete, assignment reconcile), not a fresh attach, and Fleet Autonomy declares no provisioning category since HIER-P2DECL
system-platform-resilience System Concierge + Capacity Manager Operator chat door (Concierge) and the capacity keeper's scale / drain arm (System::Platform::ReplicaReconciler resolves system.platform.scale_out / scale_in against the executing agent — HIER-P2B)

Pattern: Plan vs Execute

Every executor implements:

def self.descriptor → { name, description, category, inputs, outputs }
def initialize(account:, agent:, user:)
def execute(**inputs) → { success: true, data: {...} } | { success: false, error: "..." }

Most fleet/SDWAN executors support dry_run: true mode — return the plan without side effects. The Fleet Autonomy reconciler uses dry-run for sensor analysis, then commits via dry_run: false once approval policies allow.

Per-Executor Reference

attribute_failure — Failure analysis

Source: attribute_failure_executor.rb Category: sre_observability (subdomain: fleet) Inputs: instance_id (required), lookback_hours (default 24) Outputs: candidates, top_candidate, confidence, reasoning

Given a failed NodeInstance, ranks recent module changes + version promotions in the lookback window by likelihood of being the cause. Returns a structured rationale operators can read in chat or paste into a postmortem.

capacity_recommend — Fleet sizing

Source: capacity_recommend_executor.rb Category: sre_observability (subdomain: fleet) Inputs: template_id (required), target_min_active (default from constant) Outputs: template_id, total_count, active_count, silent_count, errored_count, recommendation, confidence

Looks at heartbeat health + module assignment density across a Template's instances. Returns a sized recommendation (e.g. "+2 instances") with a confidence label.

cve_response — CVE triage

Source: cve_response_executor.rb Category: security (subdomain: cve) Inputs: cve_id, severity, affected_packages, summary (optional) Outputs: risk_score, exposed_modules, exposed_instance_count, remediation_plan, requires_approval

Triages a CVE against the fleet — enumerates exposure, scores risk, proposes a remediation plan. Sets requires_approval=true when the plan touches >5% of fleet instances.

cve_runbook_generate — CVE remediation runbook

Source: cve_runbook_generate_executor.rb Category: security (subdomain: cve) Inputs: cve_id, persist_as_page (default false) Outputs: runbook_markdown, exposed_module_count, exposed_instance_count, risk_score, requires_approval, persisted_page_id

Generates a markdown remediation runbook for a CVE — exposed modules, recommended steps, verification commands. Optionally persists as a Pages document for operator team review.

docker_provision (Phase 1) — Docker daemon provisioning

Source: docker_provision_executor.rb Category: devops (subdomain: runtime) Inputs: node_instance_id (required), dry_run (default false) Outputs: host_id, host_status, api_endpoint, already_provisioned, plan (dry_run only)

Wraps System::DockerDaemonProvisionerService.provision! for skill-based dispatch. Idempotent — already_provisioned: true on re-call. Bound to Runtime Manager.

drift_remediate — Module reconciliation

Source: drift_remediate_executor.rb Category: sre_observability (subdomain: fleet) Inputs: instance_id (required), max_disruption_pct (default 20) Outputs: resolved, requires_approval, disruption_pct, planned_actions: { attach, detach, update }

Reconciles a NodeInstance's running modules against its assigned modules. Returns planned attach/detach/update actions with disruption %. Sets requires_approval=true when disruption exceeds threshold.

The executor applies nothing, and nothing applies its plan. resolved is true only when there was no drift to begin with; whenever a plan is returned it is false, whatever requires_approval says.

Three DecisionEngine bindings invoke this executor. system.module_drift and system.config_drift go on to dispatch a System::Task (sync_modules / apply_config) — but only when requires_approval is false, which #dispatch_reconcile_task re-reads as its auto-apply budget. system.instance_silent reboots the instance and ignores the result entirely.

The dispatched task is not an instruction to perform planned_actions. It copies them into its options, where nothing reads them: the on-node agent's sync handler takes only force_resync and module_id and calls Reconciler.RunOnce, re-deriving the desired set on the node. The same drift converges, by the generic reconcile the 60s tick already runs. Treat planned_actions as an operator-facing preview (it is what the ApprovalRequest displays), and poll the task — not this return — to learn whether the fleet converged.

module_compose — Template draft from workload description

Source: module_compose_executor.rb Category: devops (subdomain: modules) Inputs: description (free text), platform_id (optional), max_modules Outputs: draft_template, conflicts, candidate_count, reasoning

Ranks modules against a workload description semantically (embedding cosine similarity, falling back to keyword overlap when the embedding provider can't embed), then runs conflict checks and surfaces package gaps to materialize when no module covers a capability. Useful when an operator describes a workload ("nginx with TLS") and wants a starter Template draft.

provision_cluster (Phase 2) — Cluster bootstrap

Source: provision_cluster_executor.rb Category: devops (subdomain: fleet) Inputs: template_id, count (1-50), provider_region_id, provider_instance_type_id, name_prefix, dry_run Outputs: count, created_nodes, provisioned, failures, partial, plan (dry_run only)

Composes system_create_node + system_provision_instance per node. Hard cap at 50 instances per call — larger rolls go through rolling_module_upgrade with explicit operator approval. Bound to Runtime Manager.

rolling_module_upgrade — Fleet-atomic module upgrade

Source: rolling_module_upgrade_executor.rb Category: release_management (subdomain: modules) Inputs: template_id, module_id, target_version_id, max_consecutive_failures, health_timeout_sec Outputs: total_instances, affected_instance_ids, estimated_total_seconds, circuit_breaker

Sizes a module upgrade. Two independent limits, and both matter:

FLEET-ATOMIC — the upgrade cannot be batched or staged. The version an instance receives resolves from NodeModule#current_version_id, a per-module pointer read at download, and system_node_module_assignments (the only per-node row for a module) carries no version column of any kind. So there is no per-instance version selection to batch over: every instance carrying the module converges together. batch_pct was removed from this executor's contract in IMP-b948ea7fa382 rather than kept as an accepted-and- ignored input. For a genuine blast-radius bound, separate the scope — instance pools, or a second NodeModule row with its own pointer (Tutorial 06 § If you need a real blast-radius bound).

PLAN ONLY — NOT IMPLEMENTED beyond that point: the executor returns the plan and nothing executes it. There is no advancer or circuit breaker for module upgrades; max_consecutive_failures and health_timeout_sec are echoed into the returned circuit_breaker hash (status: "not_implemented") and read by nothing. Operator procedure: Tutorial 06 § What to do instead.

For a batched rollout that IS implemented, see boot_image_drift_rollout — it dispatches each batch and converges tick-by-tick off its own drift sensor. Batching works there because boot images are selected per-instance; modules are not.

runbook_generate — Template runbook

Source: runbook_generate_executor.rb Category: documentation (subdomain: docs) Inputs: template_id, persist_as_page (default false) Outputs: runbook_markdown, section_count, persisted_page_id, source_artifacts

Generates a markdown operational runbook for a Template — boot order, common failure modes, recovery procedures. Optionally persists as a Pages document.

sdwan_bgp_session_remediate — iBGP session triage

Source: sdwan_bgp_session_remediate_executor.rb Category: sre_observability (subdomain: sdwan) Inputs: bgp_session_id OR (peer_id + neighbor_address), dry_run (default true) Outputs: resolved, session_id, state, likely_cause, recommended_action

Triages an unhealthy iBGP session. v1 returns analysis only — does NOT auto-restart FRR. Operators run the recommended command after review.

sdwan_failover — Hub failover planning

Source: sdwan_failover_executor.rb Category: sre_observability (subdomain: sdwan) Inputs: network_id, dry_run (default true) Outputs: resolved, network_id, current_hub_count, candidate_count, candidates

Identifies hub-promotion candidates when a network's hub is unreachable. Returns spokes ranked by last_handshake_at. v1 only supports planning — operator manually flips publicly_reachable=true after review.

sdwan_peer_remediate — Peer key rotation

Source: sdwan_peer_remediate_executor.rb Category: sre_observability (subdomain: sdwan) Inputs: peer_id, dry_run (default false) Outputs: resolved, rotated_from_key_id, new_key_id, new_public_key

Rotates an SDWAN peer's WireGuard keypair. The agent re-establishes the tunnel from a clean key on its next reconcile.

sdwan_credential_refresh — Membership credential re-issue

Source: sdwan_credential_refresh_executor.rb Category: sdwan Inputs: peer_id, dry_run (default false) Outputs: resolved, membership_credential_id, revision, not_after

Re-issues an SDWAN peer's expiring MembershipCredential server-side via Sdwan::MembershipCredentialSigner.ensure_fresh!, so a fresh envelope is ready for the agent's next pull. Deliberately never touches the WireGuard keypair — rotating the key revokes the active pubkey and cuts the working tunnel of exactly the peer that isn't polling (IMP-df40782d3f4d). Serves system.sdwan_credential_expiring via the DecisionEngine.

sdwan_vip_failover — Virtual IP failover

Source: sdwan_vip_failover_executor.rb Category: sre_observability (subdomain: sdwan) Inputs: virtual_ip_id, dry_run (default false) Outputs: resolved, virtual_ip_id, previous_holder_peer_id, new_holder_peer_id, anycast

Promotes the next failover candidate of a silent-holder VIP. Anycast VIPs return informational only (failover handled by routing).

How Executors Get Invoked

Path A — direct skill execution (chat agent):

operator chat → System Concierge → discover_skills(task) → get_skill_context(slug)
  → build args from chat context → System::Ai::Skills::*Executor.new.execute(...)

Path B — autonomy decision loop (monitor agent):

Fleet Autonomy reconciler → sensors emit signals → DecisionEngine
  → policy match (auto_approve / notify_and_proceed / require_approval)
  → if allowed: System::Ai::Skills::*Executor.new.execute(...)
  → if require_approval: ApprovalRequest queued, operator reviews

Path C — workspace task (multi-agent coordination):

Workspace mission → spawn task → agent picks executor by name
  → execute(...) returns structured result → next task in mission

Adding a New Executor

  1. Create extensions/system/server/app/services/system/ai/skills/<name>_executor.rb. Match the canonical shape:
    module System::Ai::Skills
      class FoobarExecutor
        def self.descriptor = { name: "foobar", description: "...", category: "...", inputs: {...}, outputs: {...} }
        def initialize(account:, agent: nil, user: nil)
        def execute(**inputs) = { success: bool, data: {...} } | { success: false, error: "..." }
      end
    end
  2. Add the skill to extensions/system/server/db/seeds/system_skills_seed.rb. The executor's own descriptor[:category] is one of the 8 executor categories (Devops / Documentation / Federation / Fleet / Governance / Sdwan / Security / System); the seed separately maps it to a platform Ai::Skill enum value (devops, security, sre_observability, release_management, documentation) for cross-platform discovery. Keep the two consistent in intent. See SKILL_EXECUTOR_AUTHORING.md for the full skill_descriptor + binds_to contract.
  3. Bind to an agent in the appropriate seed (system_concierge_agent.rb, fleet_autonomy_agent.rb, or system_runtime_manager_agent.rb).
  4. Re-run seeds: cd server && bundle exec rails db:seed.
  5. Verify discoverability: platform.discover_skills query: "your task" should return the new skill.

Example Inputs and Outputs

Every executor returns { success: true, data: {...} } on the happy path or { success: false, error: "..." } on failure. The data shape per executor:

attribute_failure

// Input
{ "instance_id": "0193cdef-1234-7890-abcd-001122334455", "lookback_hours": 24 }

// Output (success.data)
{
  "candidates": [
    { "kind": "module_promotion", "module": "nginx", "from_version": "1.24.0", "to_version": "1.26.0",
      "promoted_at": "2026-05-04T08:12:30Z", "score": 0.74,
      "reason": "version promoted within 1.5h before instance went silent" },
    { "kind": "module_assignment_change", "module": "tls-config", "action": "attached",
      "changed_at": "2026-05-04T08:55:12Z", "score": 0.41 }
  ],
  "top_candidate": { "kind": "module_promotion", "module": "nginx", "score": 0.74 },
  "confidence": "medium",
  "reasoning": "Most recent change in lookback window: nginx 1.24→1.26 promoted at 08:12; instance silent at 09:30. Module-promote pattern with high recency."
}

capacity_recommend

// Input
{ "template_id": "tmpl-abc-7890", "target_min_active": 3 }

// Output (success.data)
{
  "template_id": "tmpl-abc-7890",
  "total_count": 5,
  "active_count": 2,
  "silent_count": 2,
  "errored_count": 1,
  "recommendation": { "action": "scale_up", "delta": 1, "rationale": "active=2 < target_min_active=3" },
  "confidence": "low"
}

confidence: "low" is the v0 default — real telemetry (M-D2-2) will lift this.

cve_response

// Input
{
  "cve_id": "CVE-2026-12345",
  "severity": "critical",
  "affected_packages": [{ "name": "openssl", "version": "<3.1.4" }],
  "summary": "Buffer overflow in OpenSSL TLS handshake"
}

// Output (success.data)
{
  "cve_id": "CVE-2026-12345",
  "severity": "critical",
  "risk_score": 85,
  "exposed_modules": [
    { "id": "mod-abc", "name": "system-base", "assignment_count": 12 },
    { "id": "mod-def", "name": "nginx",        "assignment_count": 8 }
  ],
  "exposed_instance_count": 20,
  "remediation_plan": {
    "actions": [
      { "module": "system-base", "from_version": "1.0.3", "to_version": "1.0.4", "instance_count": 12 },
      { "module": "nginx",       "from_version": "1.24.0", "to_version": "1.26.0", "instance_count": 8 }
    ],
    "estimated_seconds": 2400
  },
  "requires_approval": true
}

requires_approval=true because risk_score ≥ AUTO_GATE_RISK_THRESHOLD (50).

cve_runbook_generate

// Input
{ "cve_id": "CVE-2026-12345", "persist_as_page": true }

// Output (success.data)
{
  "runbook_markdown": "# CVE-2026-12345 — Remediation Runbook\n\n## Exposure\n\n- 2 NodeModules affected (system-base, nginx)\n- 20 NodeInstances exposed\n\n## Steps\n\n1. ...\n",
  "exposed_module_count": 2,
  "exposed_instance_count": 20,
  "risk_score": 85,
  "requires_approval": true,
  "persisted_page_id": "page-cve-2026-12345"
}

docker_provision

// Input (live)
{ "node_instance_id": "0193cdef-1234-7890-abcd-001122334455", "dry_run": false }

// Output (success.data, live)
{
  "dry_run": false,
  "host_id": "host-9876",
  "host_status": "pending",
  "api_endpoint": "tcp://[fd00:abcd:1::42]:2376",
  "already_provisioned": false
}

// Output (success.data, dry_run)
{
  "dry_run": true,
  "plan": {
    "node_instance_id": "0193cdef-...",
    "sdwan_peer_address": "fd00:abcd:1::42",
    "actions": [
      "Mint client mTLS cert via InternalCaService",
      "Create Devops::DockerHost row (status=pending)",
      "Wait for agent to install docker-ce + report phase=ready"
    ]
  }
}

already_provisioned: true is returned (with no side effects) when a managed host already exists — idempotent.

drift_remediate

// Input
{ "instance_id": "0193cdef-1234-7890-abcd-001122334455", "max_disruption_pct": 20 }

// Output (success.data, drift detected)
{
  "resolved": false,
  "requires_approval": false,
  "disruption_pct": 20,
  "planned_actions": {
    "attach": ["security-hardening"],
    "detach": [],
    "update": ["nginx (1.24.0 → 1.26.0)"]
  },
  "note": "plan only, nothing applied: within the auto-apply budget, so the fleet lane may dispatch a sync_modules task for these actions",
  "drift_report": { "/* full system_drift_report payload */": null }
}

// Output (success.data, no drift)
{ "resolved": true, "requires_approval": false, "disruption_pct": 0, "planned_actions": { "attach": [], "detach": [], "update": [] }, "reason": "no drift" }

5 changes ≈ 100% disruption (linear v0 model). requires_approval=true when disruption_pct > max_disruption_pct.

module_compose

// Input
{ "description": "nginx with TLS termination + rate limiting", "platform_id": "platform-abc", "max_modules": 5 }

// Output (success.data)
{
  "draft_template": {
    "name_suggestion": "nginx-tls-rate-limited",
    "modules": [
      { "name": "system-base", "priority": 10, "reason": "always required" },
      { "name": "security-hardening", "priority": 20, "reason": "TLS hardening baseline" },
      { "name": "nginx", "priority": 50, "reason": "keyword match: nginx" }
    ]
  },
  "conflicts": [],
  "candidate_count": 3,
  "reasoning": "Matched 'nginx' (nginx module), 'TLS' (security-hardening). Rate limiting requires custom config — recommend operator add a config-variety override module."
}

provision_cluster

// Input (live)
{
  "template_id": "tmpl-k3s-template",
  "count": 3,
  "provider_region_id": "region-aws-us-east-1",
  "provider_instance_type_id": "type-t3-medium",
  "name_prefix": "k3s-prod",
  "dry_run": false
}

// Output (success.data, live)
{
  "count": 3,
  "created_nodes": ["node-1", "node-2", "node-3"],
  "provisioned": 3,
  "failures": [],
  "partial": false
}

// Output (success.data, dry_run)
{
  "count": 3,
  "plan": {
    "actions": [
      "Create 3 Node rows with name_prefix=k3s-prod",
      "Provision 3 NodeInstances in region us-east-1, type t3.medium",
      "First instance gets k3s-server module assignment; remaining get k3s-agent"
    ],
    "estimated_seconds": 600
  }
}

Hard-capped at 50 per call — larger fleets go through rolling_module_upgrade.

rolling_module_upgrade

// Input — there is no batch_pct: the upgrade is FLEET-ATOMIC.
{ "template_id": "tmpl-abc", "module_id": "mod-nginx", "target_version_id": "v-1.26.0",
  "max_consecutive_failures": 2, "health_timeout_sec": 300 }

// Output (success.data) — a plan. Nothing executes it.
{
  "total_instances": 50,
  "affected_instance_ids": ["...", "...50 ids — they all move together"],
  "estimated_total_seconds": 6000,
  "circuit_breaker": { "trips_after_consecutive_failures": 2,
                       "health_timeout_sec": 300,
                       "status": "not_implemented" },
  "requires_approval": true,
  "executed": false,
  "note": "PLAN ONLY — nothing moves the fleet from this plan, and the upgrade is FLEET-ATOMIC ..."
}

NOT IMPLEMENTED — nothing executes this plan. Nothing reads it and nothing moves the pointer from it; max_consecutive_failures / health_timeout_sec are echoed into the circuit_breaker hash above and read by nothing.

FLEET-ATOMIC — and there is no batch_pct to size. The version an instance receives resolves from NodeModule#current_version_id — a per-module pointer — and system_node_module_assignments has no version column, so moving the pointer moves every instance carrying that module at once. affected_instance_ids is that whole population, not a first batch. Real staging means separating the scope: an instance pool, or a second NodeModule row with its own pointer. See docs/tutorials/06-rolling-upgrade.md for the manual procedure that does work.

runbook_generate

// Input
{ "template_id": "tmpl-abc", "persist_as_page": true }

// Output (success.data)
{
  "runbook_markdown": "# nginx-tls Runbook\n\n## Boot order\n\n1. system-base\n2. security-hardening\n3. nginx\n\n## Common failure modes\n\n- ...\n",
  "section_count": 6,
  "persisted_page_id": "page-tmpl-abc-runbook",
  "source_artifacts": ["module_manifest:system-base", "module_manifest:nginx"]
}

sdwan_bgp_session_remediate

// Input
{ "bgp_session_id": "bgp-sess-abc", "dry_run": true }

// Output (success.data)
{
  "resolved": false,
  "session_id": "bgp-sess-abc",
  "state": "idle",
  "likely_cause": "wrong AS number on neighbor (expected 65000, observed 65001)",
  "recommended_action": "vtysh -c 'show ip bgp summary' on the holding peer to confirm; then `clear ip bgp <neighbor>` to force re-handshake"
}

v1 is planning-only — never auto-restarts FRR. The recommended clear ip bgp command is operator-driven.

sdwan_failover

// Input
{ "network_id": "sdwan-net-abc", "dry_run": true }

// Output (success.data)
{
  "resolved": false,
  "network_id": "sdwan-net-abc",
  "current_hub_count": 1,
  "candidate_count": 2,
  "candidates": [
    { "peer_id": "peer-spoke-A", "last_handshake_at": "2026-05-04T09:30:12Z", "score": 0.92 },
    { "peer_id": "peer-spoke-B", "last_handshake_at": "2026-05-04T09:28:55Z", "score": 0.84 }
  ]
}

sdwan_peer_remediate

// Input
{ "peer_id": "peer-abc", "dry_run": false }

// Output (success.data)
{
  "resolved": true,
  "rotated_from_key_id": "key-old-abc",
  "new_key_id": "key-new-def",
  "new_public_key": "AbCd...EfGh="
}

The agent picks up the new key on its next reconcile (~30 s) and re-establishes the WireGuard tunnel.

sdwan_vip_failover

// Input (single-holder VIP, live)
{ "virtual_ip_id": "vip-abc", "dry_run": false }

// Output (success.data)
{
  "resolved": true,
  "virtual_ip_id": "vip-abc",
  "previous_holder_peer_id": "peer-old",
  "new_holder_peer_id": "peer-new",
  "anycast": false
}

// Output for anycast VIP
{
  "resolved": false,
  "virtual_ip_id": "vip-abc",
  "previous_holder_peer_id": null,
  "new_holder_peer_id": null,
  "anycast": true,
  "note": "Anycast VIPs use routing for failover; this skill is informational only for anycast."
}

Related Docs

Last verified: 2026-08-04