Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This directory contains detailed architecture documentation for kagent. Start wi
| [prompt-templates.md](prompt-templates.md) | Prompt template system with ConfigMap includes and variable interpolation |
| [data-flow.md](data-flow.md) | End-to-end request flow from UI to agent and back |
| [crds-and-types.md](crds-and-types.md) | All Custom Resource Definitions and their relationships |
| [scheduled-runs.md](scheduled-runs.md) | ScheduledRun CRD: cron-based agent triggering, design and known issues |

---

Expand Down
499 changes: 499 additions & 0 deletions docs/architecture/scheduled-runs.md

Large diffs are not rendered by default.

222 changes: 222 additions & 0 deletions go/api/config/crd/bases/kagent.dev_scheduledruns.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
name: scheduledruns.kagent.dev
spec:
group: kagent.dev
names:
kind: ScheduledRun
listKind: ScheduledRunList
plural: scheduledruns
singular: scheduledrun
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.schedule
name: Schedule
type: string
- jsonPath: .spec.suspend
name: Suspend
type: boolean
- jsonPath: .status.lastRunTime
name: Last Run
type: date
- jsonPath: .status.nextRunTime
name: Next Run
type: string
name: v1alpha2
schema:
openAPIV3Schema:
description: ScheduledRun is the Schema for the scheduledruns API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: ScheduledRunSpec defines the desired state of ScheduledRun.
properties:
agentRef:
description: |-
AgentRef is a reference to the Agent to execute. If Namespace is empty
it defaults to the ScheduledRun's namespace.
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
maxRunHistory:
default: 10
description: MaxRunHistory is the maximum number of run history entries
to retain.
maximum: 100
minimum: 1
type: integer
prompt:
description: Prompt is the text prompt to send to the agent on each
run.
minLength: 1
type: string
schedule:
description: |-
Schedule is a cron expression defining when to run the agent. Standard
5-field cron syntax (minute hour day-of-month month day-of-week).
minLength: 1
type: string
suspend:
default: false
description: |-
Suspend pauses cron-driven scheduling when set to true. Manual triggers
via the API still execute; Suspend only gates the cron tick path.
type: boolean
timeZone:
description: |-
TimeZone is an IANA time zone name (e.g. "America/Los_Angeles") used
to interpret Schedule. If empty, the controller process's local time
zone (typically UTC in-cluster) is used.
type: string
required:
- agentRef
- prompt
- schedule
type: object
status:
description: ScheduledRunStatus defines the observed state of ScheduledRun.
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
lastRunTime:
format: date-time
type: string
nextRunTime:
format: date-time
type: string
observedGeneration:
format: int64
type: integer
runHistory:
items:
description: |-
RunHistoryEntry records one execution of a scheduled run. DispatchStatus
is set synchronously when the A2A call returns; Outcome is set
asynchronously by polling the session.
properties:
completionTime:
format: date-time
type: string
dispatchMessage:
type: string
dispatchStatus:
description: |-
DispatchStatus reflects whether the A2A SendMessage call to the agent pod
succeeded. It says nothing about the LLM result — that lives in [RunOutcome].
enum:
- Dispatched
- DispatchFailed
type: string
outcome:
description: |-
RunOutcome reflects the terminal state of the agent run, resolved
asynchronously by polling the session's task state after dispatch returns.
"Pending" means polling is still in progress (or was abandoned because the
controller restarted before the session terminated).
enum:
- Pending
- Succeeded
- Failed
- Timeout
type: string
outcomeMessage:
type: string
outcomeTime:
format: date-time
type: string
sessionId:
type: string
startTime:
format: date-time
type: string
required:
- dispatchStatus
- startTime
type: object
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
165 changes: 165 additions & 0 deletions go/api/v1alpha2/scheduledrun_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

// AnnotationCreatedBy records the user identity that created a ScheduledRun.
// The scheduler uses this value as the session userID so the user who created
// the schedule can read the resulting session in the UI.
const AnnotationCreatedBy = "kagent.dev/created-by"

// DispatchStatus reflects whether the A2A SendMessage call to the agent pod
// succeeded. It says nothing about the LLM result — that lives in [RunOutcome].
// +kubebuilder:validation:Enum=Dispatched;DispatchFailed
type DispatchStatus string

const (
// DispatchStatusDispatched means the A2A SendMessage call returned without
// error. The session was created and the prompt was accepted by the agent
// pod. The model invocation result is recorded separately in Outcome.
DispatchStatusDispatched DispatchStatus = "Dispatched"
// DispatchStatusFailed means dispatch itself failed: session create
// error, A2A client error, agent pod 5xx, or panic in the dispatch path.
DispatchStatusFailed DispatchStatus = "DispatchFailed"
)

// RunOutcome reflects the terminal state of the agent run, resolved
// asynchronously by polling the session's task state after dispatch returns.
// "Pending" means polling is still in progress (or was abandoned because the
// controller restarted before the session terminated).
// +kubebuilder:validation:Enum=Pending;Succeeded;Failed;Timeout
type RunOutcome string

const (
// RunOutcomePending means the run was dispatched but no terminal task
// state has been observed yet. Either polling is in progress, or the
// controller restarted before resolution and the entry is now orphaned.
RunOutcomePending RunOutcome = "Pending"
// RunOutcomeSucceeded means the session's last task reached
// TaskStateCompleted.
RunOutcomeSucceeded RunOutcome = "Succeeded"
// RunOutcomeFailed means the session's last task reached a non-success
// terminal state (failed, canceled, rejected).
RunOutcomeFailed RunOutcome = "Failed"
// RunOutcomeTimeout means polling exceeded the configured budget without
// observing a terminal state.
RunOutcomeTimeout RunOutcome = "Timeout"
)

// AgentReference holds a reference to an Agent resource. AgentRef.Namespace
// may name a namespace different from the ScheduledRun's own — operators are
// responsible for ensuring the cross-namespace reference is intended (the
// controller does not enforce namespace boundaries).
type AgentReference struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"` // +optional
}

// ScheduledRunSpec defines the desired state of ScheduledRun.
type ScheduledRunSpec struct {
// Schedule is a cron expression defining when to run the agent. Standard
// 5-field cron syntax (minute hour day-of-month month day-of-week).
// +kubebuilder:validation:MinLength=1
Schedule string `json:"schedule"`

// TimeZone is an IANA time zone name (e.g. "America/Los_Angeles") used
// to interpret Schedule. If empty, the controller process's local time
// zone (typically UTC in-cluster) is used.
// +optional
TimeZone string `json:"timeZone,omitempty"`

// AgentRef is a reference to the Agent to execute. If Namespace is empty
// it defaults to the ScheduledRun's namespace.
AgentRef AgentReference `json:"agentRef"`

// Prompt is the text prompt to send to the agent on each run.
// +kubebuilder:validation:MinLength=1
Prompt string `json:"prompt"`

// Suspend pauses cron-driven scheduling when set to true. Manual triggers
// via the API still execute; Suspend only gates the cron tick path.
// +optional
// +kubebuilder:default=false
Suspend bool `json:"suspend,omitempty"`

// MaxRunHistory is the maximum number of run history entries to retain.
// +optional
// +kubebuilder:default=10
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=100
MaxRunHistory int `json:"maxRunHistory,omitempty"`
}

// RunHistoryEntry records one execution of a scheduled run. DispatchStatus
// is set synchronously when the A2A call returns; Outcome is set
// asynchronously by polling the session.
type RunHistoryEntry struct {
StartTime metav1.Time `json:"startTime"`
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
DispatchStatus DispatchStatus `json:"dispatchStatus"`
DispatchMessage string `json:"dispatchMessage,omitempty"`
SessionID string `json:"sessionId,omitempty"`
Outcome RunOutcome `json:"outcome,omitempty"`
OutcomeMessage string `json:"outcomeMessage,omitempty"`
OutcomeTime *metav1.Time `json:"outcomeTime,omitempty"`
}

// ScheduledRunStatus defines the observed state of ScheduledRun.
type ScheduledRunStatus struct {
LastRunTime *metav1.Time `json:"lastRunTime,omitempty"`
NextRunTime *metav1.Time `json:"nextRunTime,omitempty"`
RunHistory []RunHistoryEntry `json:"runHistory,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule",type="string",JSONPath=".spec.schedule"
// +kubebuilder:printcolumn:name="Suspend",type="boolean",JSONPath=".spec.suspend"
// +kubebuilder:printcolumn:name="Last Run",type="date",JSONPath=".status.lastRunTime"
// +kubebuilder:printcolumn:name="Next Run",type="string",JSONPath=".status.nextRunTime"
// +kubebuilder:storageversion

// ScheduledRun is the Schema for the scheduledruns API.
type ScheduledRun struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ScheduledRunSpec `json:"spec,omitempty"`
Status ScheduledRunStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ScheduledRunList contains a list of ScheduledRun.
type ScheduledRunList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ScheduledRun `json:"items"`
}

func init() {
SchemeBuilder.Register(func(s *runtime.Scheme) error {
s.AddKnownTypes(GroupVersion, &ScheduledRun{}, &ScheduledRunList{})
return nil
})
}
Loading