Skip to content

Setup recurring

Configure a recurring cost-reduction agent through a progressive Q-and-A wizard. Each call merges the latest answer into a session (keyed on session_id) and either asks the next missing question or, once all answers are in, emits policy.yaml plus the chosen scheduler manifest (Kubernetes CronJob, GitHub Actions workflow, or crontab + wrapper script) with step-by-step apply instructions.

Example

You

set up a weekly 30% savings tick

Log10x

Wizard Q1: which services should the policy target?

Pass target_services: [] for all, or a named list.

Re-invoke with session_id: "recurring-lx8a-9k2pq3".

More to ask

  • "recurring policy: payments and checkout, 40%, daily 3am UTC"
  • "emit a k8s CronJob that trims 25% off cart-svc weekly"
  • "schedule a GitHub Actions tick every 6h against acme/log10x-config"

Prerequisites

Reporter deployed (metrics drive the per-pattern planner that the tick invokes). A reachable gitops repo or local path for policy.yaml, and credentials for the chosen scheduler (kubectl access for k8s_cron, repo write for github_actions, host shell for crontab).

Schema and samples

Input example

Representative call (synthetic, not captured from the live demo env).

{
  "target_services": ["payment", "checkout"],
  "target_percent": 30,
  "schedule": "daily-03utc",
  "scheduler": "k8s_cron",
  "config_plane": "https://github.com/acme/log10x-config",
  "confirm": true
}
Input schema

Agent-facing JSON Schema (the canonical shape the MCP server publishes via tools/list):

{
  "type": "object",
  "properties": {
    "session_id": {
      "type": "string",
      "description": "Wizard session handle. Omit on the first call — a new session is minted and returned. Pass it back unchanged on every subsequent call."
    },
    "target_services": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Services the policy targets. Empty array = all services. Use service names from `log10x_services`. The wizard asks if omitted."
    },
    "target_percent": {
      "type": "integer",
      "minimum": 1,
      "maximum": 95,
      "description": "Desired savings target, as a percentage of current log volume (1-95). Default: 30. The CLI's per-pattern planner works backward from this target."
    },
    "schedule": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "daily-03utc",
            "every-6h",
            "every-12h",
            "every-24h-localtz"
          ]
        },
        {
          "type": "string",
          "pattern": "^[\\d*,\\-\\/\\s]+$"
        }
      ],
      "description": "Tick schedule. Presets: daily-03utc (default), every-6h, every-12h, every-24h-localtz. Or pass a raw 5-field cron expression (e.g. \"0 5 * * 1\")."
    },
    "scheduler": {
      "type": "string",
      "enum": [
        "k8s_cron",
        "github_actions",
        "crontab"
      ],
      "description": "Where the recurring tick runs. k8s_cron = Kubernetes CronJob (default when kubectl reachable), github_actions = GHA workflow, crontab = crontab + wrapper script."
    },
    "config_plane": {
      "type": "string",
      "minLength": 1,
      "description": "Gitops repo URL (e.g. https://github.com/acme/log10x-config) or local path where the recurring CLI reads policy.yaml and writes updated cap CSVs."
    },
    "exceptions": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Service names the policy must never touch (audit / regulatory / executive). Optional — defaults to empty. Pass [] to explicitly clear all exceptions."
    },
    "min_delta_pp": {
      "type": "integer",
      "minimum": 0,
      "maximum": 50,
      "description": "Minimum change in savings percentage points required before the tick commits a new CSV. Default: 2. Set to 0 to commit on every tick."
    },
    "env_id": {
      "type": "string",
      "description": "Log10x environment ID. Auto-detected from LOG10X_ENV_ID when absent."
    },
    "namespace": {
      "type": "string",
      "description": "Kubernetes namespace for the CronJob. Default: log10x. Only used when scheduler=k8s_cron."
    },
    "secret_name": {
      "type": "string",
      "description": "Name of the Kubernetes Secret holding LOG10X_API_KEY. Default: log10x-secret. Only used when scheduler=k8s_cron."
    },
    "confirm": {
      "type": "boolean",
      "description": "Set to true to confirm the configuration and emit the artifacts. The wizard asks for confirmation interactively when omitted."
    }
  },
  "additionalProperties": false
}

Source: src/tools/setup-recurring.ts.

Output example

Representative envelope (synthetic, not captured from the live demo env). view: "summary" returns the full StructuredOutput with typed data. Long YAML bodies trimmed for readability; the real call returns them in full.

Headline (the 1-line agent-facing answer):

Recurring policy emitted: log10x-cronjob.yaml + policy.yaml ready

{
  "schema_version": "1.0",
  "schema_epoch": "2026-05-25",
  "tool": "log10x_setup_recurring",
  "view": "summary",
  "summary": {
    "headline": "Recurring policy emitted: log10x-cronjob.yaml + policy.yaml ready"
  },
  "data": {
    "mode": "emit",
    "ok": true,
    "session_id": "recurring-lx8a-9k2pq3",
    "policy_yaml": "target_services:\n  - payment\n  - checkout\ntarget_percent: 30\nschedule: daily-03utc\nmin_delta_pp: 2\n...",
    "scheduler_manifest": "apiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: log10x-recurring\n  namespace: log10x\nspec:\n  schedule: \"0 3 * * *\"\n...",
    "scheduler_manifest_filename": "log10x-cronjob.yaml",
    "apply_instructions": "### Apply instructions: Kubernetes CronJob\n\n1. Commit `policy.yaml` to the root of your gitops repo...",
    "markdown": "## Recurring cost-reduction policy: artifacts ready\n\n| Field | Value |\n|---|---|\n| Services | payment, checkout |\n...",
    "human_summary": "Recurring policy emitted for 2 services targeting 30% savings via k8s_cron. Artifacts: policy.yaml + log10x-cronjob.yaml. Apply instructions are in data.apply_instructions. Run log10x_commitment_report after the first week to verify realized savings."
  },
  "actions": [
    {
      "tool": "log10x_doctor",
      "args": {},
      "reason": "confirm the environment is healthy before the first tick runs",
      "role": "optional-followup"
    }
  ],
  "truncated": false,
  "warnings": []
}
Output schema

The data block inside the StructuredOutput envelope. The shape is a discriminated union on mode: next_question while the wizard is still gathering answers, emit once all questions are answered.

type ToolData =
  | {
      mode: 'next_question';
      ok: false;
      session_id: string;
      question_id:
        | 'target_services'
        | 'target_percent'
        | 'schedule'
        | 'scheduler'
        | 'config_plane'
        | 'exceptions'
        | 'confirm';
      markdown: string;
      human_summary: string;
    }
  | {
      mode: 'emit';
      ok: true;
      session_id: string;
      policy_yaml: string;
      scheduler_manifest: string;
      scheduler_manifest_filename: string;
      apply_instructions: string;
      crontab_wrapper_script?: string;
      markdown: string;
      human_summary: string;
    };

Envelope-level fields the agent should also read: summary.headline (1-line answer), actions[] (next-call chain hints as {tool, args, reason}required-next while questions remain, optional-followup pointing at log10x_doctor after emit), truncated: boolean, images[] (PNG attachments where applicable), schema_epoch (engine-ID stability boundary).