Skip to main content

Scheduler

The scheduler runs Dynamic Agent chats on recurring cron schedules or as delayed one-off jobs. It is disabled by default. Enabling it installs:

  • caipe-scheduler, which stores schedules in MongoDB and manages Kubernetes CronJobs and Jobs.
  • mcp-scheduler, which exposes schedule operations to Dynamic Agents.
  • caipe-cron-runner, the short-lived container started by each CronJob or Job.
  • CAIPE UI/BFF wiring that turns a scheduled fire into a chat owned by the user who created the schedule.

Dynamic Agents, the CAIPE UI, Keycloak, AgentGateway, and MongoDB are required. OpenFGA is optional for the scheduler itself, but is required for the admin-only policy described below.

The umbrella chart ships with the feature off:

global:
scheduler:
enabled: false

Identity and token flow

Schedule creation and scheduled execution use different credentials.

Create or manage a schedule

The live user's Keycloak JWT determines schedule ownership. Dynamic Agents resolves the scheduler MCP's caller_token credential source and places that JWT in X-CAIPE-Caller-Token. AgentGateway authenticates and authorizes the request, then forwards that header to mcp-scheduler.

mcp-scheduler does not validate or decode the JWT. It relays the opaque value to caipe-scheduler as an Authorization bearer. The scheduler validates the signature, issuer, audience, and expiry, then derives owner_sub and owner_user_id from the claims. An agent cannot choose or override the owner.

schedulerMcp.auth.mode intentionally remains none. AgentGateway is the MCP authentication boundary, while caipe-scheduler is the ownership boundary and validates the forwarded JWT itself.

Execute a scheduled run

Cron runner pods do not hold a user JWT. They authenticate to the BFF only with the shared X-Scheduler-Token. The BFF ignores any caller-supplied owner or agent identity, loads both from the authoritative MongoDB schedule record, and uses the dedicated caipe-scheduler-runner Keycloak client to mint a short-lived JWT for the stored owner.

The BFF checks that the owner can still use the selected agent and forwards the minted owner JWT to Dynamic Agents. All later AgentGateway and MCP calls therefore run with the schedule owner's current permissions. A disabled owner, removed agent grant, failed token exchange, or missing schedule record fails closed.

The shared scheduler token proves only that the request came from the scheduler subsystem. It is never accepted as user identity. Use the same Kubernetes Secret for the scheduler, scheduler MCP, cron runner, and BFF, and do not place its literal value in a values file.

Enable the scheduler

The following example uses a Helm release named caipe in namespace caipe with the bundled Keycloak. Replace the public issuer and MongoDB Secret values for your environment. The internal JWKS URL may remain cluster-local, but the issuer must exactly match the iss claim in user tokens.

tags:
caipe-ui: true
dynamic-agents: true
keycloak: true

global:
scheduler:
enabled: true
serviceTokenSecretName: caipe-scheduler-service-token
serviceTokenSecretKey: token
agentgateway:
enabled: true
static:
jwtAuth:
enabled: true
issuer: https://id.example.com/realms/caipe
jwksUrl: http://caipe-keycloak:8080/realms/caipe/protocol/openid-connect/certs
audiences:
- caipe-platform
- agentgateway

scheduler:
mongo:
# Use the same MongoDB as the CAIPE UI.
existingSecret: caipe-caipe-ui-secret
existingSecretKey: MONGODB_URI
database: caipe
auth:
jwksUrl: http://caipe-keycloak:8080/realms/caipe/protocol/openid-connect/certs
issuer: https://id.example.com/realms/caipe
audiences:
- caipe-platform

schedulerMcp:
enabled: true
auth:
mode: none

keycloak:
features:
tokenExchange: true
adminFineGrainedAuthz: true
schedulerTokenExchange:
enabled: true
botClientId: caipe-scheduler-runner

caipe-ui:
schedulerRunnerClient:
clientId: caipe-scheduler-runner
appConfig:
mcp_servers:
- id: scheduler
name: Scheduler
description: Cron-style scheduled Dynamic Agent chat runs.
transport: http
endpoint: http://caipe-agentgateway:4000/mcp/scheduler
enabled: true
credential_sources:
- kind: caller_token
target: header
name: X-CAIPE-Caller-Token

When scheduler.serviceToken.existingSecret is empty, the chart creates and preserves the Secret named by global.scheduler.serviceTokenSecretName. For an operator-managed Secret, set both locations to the same Secret name and key:

global:
scheduler:
serviceTokenSecretName: my-scheduler-service-token
serviceTokenSecretKey: token

scheduler:
serviceToken:
existingSecret: my-scheduler-service-token
existingSecretKey: token

With the bundled Keycloak, leaving keycloak.schedulerTokenExchange.secretRef and caipe-ui.schedulerRunnerClient.secretName empty makes the chart use its release-derived scheduler-runner Secret. If the Secret is managed externally, set both values to the same name. It must contain the key KC_SCHEDULER_CLIENT_SECRET:

keycloak:
schedulerTokenExchange:
secretRef: my-scheduler-runner-client

caipe-ui:
schedulerRunnerClient:
secretName: my-scheduler-runner-client
secretKey: KC_SCHEDULER_CLIENT_SECRET

Finally, grant the scheduler tools only to agents that should be able to create or manage schedules. For a config-driven agent:

caipe-ui:
appConfig:
agents:
- id: scheduled-operations
name: Scheduled Operations
enabled: true
# Add the model and prompt fields required by your deployment.
allowed_tools:
scheduler:
- create_schedule
- list_schedules
- get_schedule
- update_schedule
- pause_schedule
- resume_schedule
- restart_schedule
- schedule_one_off
- list_one_off_runs
- delete_schedule

Helm replaces lists rather than merging list entries. If mcp_servers or agents already exists in another values file, add the scheduler entries to that existing list instead of defining a second list in a later values file.

Restrict scheduler tools to organization admins

Enable OpenFGA, the AgentGateway authorization bridge, JWT validation, and the selective scheduler server check:

openfga:
enabled: true

openfgaAuthzBridge:
enabled: true

global:
agentgateway:
enabled: true
extAuth:
enabled: true
# The default service name is <release>-openfga-authz-bridge.
port: 9100

openfga-authz-bridge:
openfga:
httpUrl: http://caipe-openfga:8080
storeName: caipe-openfga
tokenValidation:
jwksUrl: http://caipe-keycloak:8080/realms/caipe/protocol/openid-connect/certs
issuer: https://id.example.com/realms/caipe
audiences:
- caipe-platform
- agentgateway
restrictedMcpServers:
- scheduler # <- This enables the admin-only check for /mcp/scheduler requests.

For every request under /mcp/scheduler, the bridge checks:

user:<JWT sub> can_invoke mcp_server:scheduler

The config-driven MCP relationship reconciler gives organization members reader and user, but deliberately removes their invoker relationship. It gives organization:<org>#admin the manager relationship. In the OpenFGA model, manager implies can_manage, and can_manage implies can_invoke. The resulting default policy is:

CallerDiscover scheduler MCPInvoke scheduler MCP
Organization memberYesNo
Organization adminYesYes

This assumes identity bootstrap or synchronization has written user:<sub> admin organization:<org> for each platform administrator.

Do not grant invoker, owner, or another relation that implies can_invoke on mcp_server:scheduler to a non-admin user or team if the intended policy is strictly admin-only.

This caller check is independent of agent tool authorization. An admin must use an agent whose allowed_tools.scheduler entries have been reconciled into the agent-to-tool OpenFGA tuples. Conversely, a non-admin is denied at the scheduler MCP server check even if the selected agent has scheduler tools.

For signed agent context and per-tool enforcement, configure the same HMAC Secret for Dynamic Agents and the bridge:

dynamic-agents:
agentContext:
existingSecret:
name: caipe-agent-context
key: CAIPE_AGENT_CONTEXT_HMAC_SECRET

openfga-authz-bridge:
agentContext:
existingSecret:
name: caipe-agent-context
key: CAIPE_AGENT_CONTEXT_HMAC_SECRET

Operational behavior

  • Schedule ownership is persisted in MongoDB. The owner supplied by an MCP tool argument or cron runner request is never trusted.
  • Cron runner pods have no Kubernetes API token and no stored user bearer.
  • Existing CronJobs are reconciled to the configured cron-runner image when the scheduler starts. The schedule version history records deployment-driven image updates.
  • Kubernetes CronJob retry and concurrency settings are controlled by the scheduler-created job template, while BFF idempotency prevents a successful scheduled conversation from being created twice for the same run.
  • If OpenFGA or the authorization bridge is unavailable, AgentGateway denies the restricted call rather than bypassing authorization.