---
title: "Setup recurring"
description: "Conversational wizard that emits policy.yaml plus a scheduler manifest for a recurring cost-reduction tick."
icon: material/calendar-clock
---

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 with step-by-step apply instructions.

The scheduler manifest is one of:

- Kubernetes CronJob
- GitHub Actions workflow
- crontab + wrapper script
- EventBridge Scheduler + CodeBuild (CloudFormation template, S3 config plane)

## :material-code-braces: Example

!!! tenx-ask "You"

    set up a weekly 30% savings tick

!!! tenx-answer "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"`.

## :material-chat-question-outline: 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"*
- *"AWS-native tick for our Lambda estate, no cluster, no GitHub"*

## :material-check-decagram-outline: Prerequisites

Reporter deployed (metrics drive the per-pattern planner that the tick invokes). A config plane for `policy.yaml`, a gitops repo, local path, or S3 prefix, and credentials for the chosen scheduler (`kubectl` access for `k8s_cron`, repo write for `github_actions`, host shell for `crontab`, CloudFormation deploy rights for `eventbridge`).

## :material-code-json: Schema and samples

??? tenx-input-example "Input example"

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

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

??? tenx-input-schema "Input schema"

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

    ```json
    {
      "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",
            "eventbridge"
          ],
          "description": "Where the recurring tick runs. k8s_cron = Kubernetes CronJob (default when kubectl reachable), github_actions = GHA workflow, crontab = crontab + wrapper script, eventbridge = AWS-native (EventBridge Scheduler + CodeBuild, S3 config plane — no cluster, no GitHub; the fit for serverless estates)."
        },
        "config_plane": {
          "type": "string",
          "minLength": 1,
          "description": "Where the recurring CLI reads policy.yaml and writes updated cap CSVs: a gitops repo URL (e.g. https://github.com/acme/log10x-config), a local path, or — required when scheduler=eventbridge — an S3 prefix (e.g. s3://acme-logs/log10x-config)."
        },
        "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`](https://github.com/log-10x/log10x-mcp/blob/main/src/tools/setup-recurring.ts).

??? tenx-output-example "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_

    ```json
    {
      "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": []
    }
    ```

??? tenx-output-schema "Output schema"

    The `data` block inside the [StructuredOutput envelope](../index.md#json-by-default-output). The shape is a discriminated union on `mode`: `next_question` while the wizard is still gathering answers, `emit` once all questions are answered.

    ```typescript
    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).
