From 3b6f3113f5a1eda8c4fdf05d68a6ca107303f0c8 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Thu, 9 Jul 2026 19:26:56 -0700 Subject: [PATCH] feat(foreman): integrate + reconcile AgenticTask kinds + slice payload Add the integrate and reconcile AgenticTaskKind values (enum widened on both AgenticTask.spec.kind and Workload pipeline steps) and the payload fields the sliced-workload pipeline needs: Slices ([]SliceRef of name/branch/files), Contract, and SharedIdentifiers ([]SharedIdentifier of id/definedBy/ referencedBy). integrate reads slices[].branch + baseBranch; reconcile reads sharedIdentifiers + slices[].files + contract. Base and integration branch reuse the existing baseBranch/branch fields. API surface only: no executor handling yet (a follow-up wires the two kinds to the slicer libraries). Regenerated deepcopy + CRD manifests + chart CRDs; the AgenticTask webhook validates only agentRef, so no webhook change. Part of #1033 Signed-off-by: Christopher Maher --- api/foreman/v1alpha1/agentictask_types.go | 72 ++++++++++++++++++- api/foreman/v1alpha1/zz_generated.deepcopy.go | 54 ++++++++++++++ .../foreman/templates/crds/agentictasks.yaml | 71 ++++++++++++++++++ charts/foreman/templates/crds/workloads.yaml | 71 ++++++++++++++++++ .../foreman.llmkube.dev_agentictasks.yaml | 71 ++++++++++++++++++ .../bases/foreman.llmkube.dev_workloads.yaml | 71 ++++++++++++++++++ 6 files changed, 409 insertions(+), 1 deletion(-) diff --git a/api/foreman/v1alpha1/agentictask_types.go b/api/foreman/v1alpha1/agentictask_types.go index 38cd5d48..c5f9dd7f 100644 --- a/api/foreman/v1alpha1/agentictask_types.go +++ b/api/foreman/v1alpha1/agentictask_types.go @@ -24,7 +24,7 @@ import ( // AgenticTaskKind is the unit of work the task performs. Each kind has a // payload shape, scheduler routing, and lifecycle. -// +kubebuilder:validation:Enum=issue-fix;verify;review;freeform +// +kubebuilder:validation:Enum=issue-fix;verify;review;freeform;integrate;reconcile type AgenticTaskKind string const ( @@ -47,6 +47,19 @@ const ( AgenticTaskKindReview AgenticTaskKind = "review" // AgenticTaskKindFreeform passes an arbitrary prompt to a named agent. AgenticTaskKindFreeform AgenticTaskKind = "freeform" + // AgenticTaskKindIntegrate unions the disjoint slice branches of a sliced + // Workload onto the current base on a fresh integration branch, gated by a + // build. Scheduled after all slice issue-fix tasks Succeed; its payload + // carries the slice branches (payload.slices[].branch) and the base + // (payload.baseBranch). Part of Sliced Workloads (#1033). + AgenticTaskKindIntegrate AgenticTaskKind = "integrate" + // AgenticTaskKindReconcile checks the integrated union against the slice + // plan's pinned shared identifiers, catching cross-slice interface drift a + // build cannot see. A pinned-missing drift is authoritative (GATE-FAIL); an + // llm-flagged-only drift is advisory. Its payload carries the pins + // (payload.sharedIdentifiers), the slices' files (payload.slices[].files), + // and the contract (payload.contract). Part of Sliced Workloads (#1033). + AgenticTaskKindReconcile AgenticTaskKind = "reconcile" ) // BranchStrategy controls how the executor cuts an issue-fix task's working @@ -351,6 +364,63 @@ type AgenticTaskPayload struct { // produced no advisories or has not yet completed. // +optional GateAdvisories []GateAdvisory `json:"gateAdvisories,omitempty"` + + // Slices are the disjoint slices of a sliced Workload. The integrate task + // unions their branches; the reconcile task checks pins against their + // files. Set on integrate and reconcile tasks. Part of #1033. + // +optional + Slices []SliceRef `json:"slices,omitempty"` + + // Contract is the slice plan's prose contract, handed to a reconcile + // task's LLM sweep as the description of the intended shared interfaces. + // Reconcile only. Part of #1033. + // +optional + Contract string `json:"contract,omitempty"` + + // SharedIdentifiers pins the exact cross-slice strings a reconcile task + // verifies against the integrated union. Reconcile only. Part of #1033. + // +optional + SharedIdentifiers []SharedIdentifier `json:"sharedIdentifiers,omitempty"` +} + +// SliceRef describes one disjoint slice of a sliced Workload. The integrate +// task unions each slice's Branch; the reconcile task verifies pinned +// identifiers appear in each slice's Files. Part of Sliced Workloads (#1033). +type SliceRef struct { + // Name is the slice's name within the plan. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + + // Branch is the git ref the slice's coder pushed. Required for the + // integrate task, which applies each slice branch's diff onto the base. + // +optional + Branch string `json:"branch,omitempty"` + + // Files are the repo-relative paths this slice owns, so a reconcile task + // looks for each pinned identifier in the right slice. + // +optional + Files []string `json:"files,omitempty"` +} + +// SharedIdentifier pins one exact string that crosses a slice boundary (a +// metric name, config key, or CRD field). The reconcile task asserts the ID +// appears verbatim in the DefinedBy slice and every ReferencedBy slice; a +// missing pin is an authoritative drift. Part of Sliced Workloads (#1033). +type SharedIdentifier struct { + // ID is the exact string every listed slice must contain verbatim. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + ID string `json:"id"` + + // DefinedBy is the slice name that must produce the identifier. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + DefinedBy string `json:"definedBy"` + + // ReferencedBy are the slice names that must consume the identifier. + // +optional + ReferencedBy []string `json:"referencedBy,omitempty"` } // AgenticTaskSpec defines the desired state of an AgenticTask. diff --git a/api/foreman/v1alpha1/zz_generated.deepcopy.go b/api/foreman/v1alpha1/zz_generated.deepcopy.go index 611f73db..4c68d59a 100644 --- a/api/foreman/v1alpha1/zz_generated.deepcopy.go +++ b/api/foreman/v1alpha1/zz_generated.deepcopy.go @@ -393,6 +393,20 @@ func (in *AgenticTaskPayload) DeepCopyInto(out *AgenticTaskPayload) { *out = make([]GateAdvisory, len(*in)) copy(*out, *in) } + if in.Slices != nil { + in, out := &in.Slices, &out.Slices + *out = make([]SliceRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SharedIdentifiers != nil { + in, out := &in.SharedIdentifiers, &out.SharedIdentifiers + *out = make([]SharedIdentifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgenticTaskPayload. @@ -968,6 +982,46 @@ func (in *ResolvedGate) DeepCopy() *ResolvedGate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SharedIdentifier) DeepCopyInto(out *SharedIdentifier) { + *out = *in + if in.ReferencedBy != nil { + in, out := &in.ReferencedBy, &out.ReferencedBy + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SharedIdentifier. +func (in *SharedIdentifier) DeepCopy() *SharedIdentifier { + if in == nil { + return nil + } + out := new(SharedIdentifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SliceRef) DeepCopyInto(out *SliceRef) { + *out = *in + if in.Files != nil { + in, out := &in.Files, &out.Files + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SliceRef. +func (in *SliceRef) DeepCopy() *SliceRef { + if in == nil { + return nil + } + out := new(SliceRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StuckLoopDetectionSpec) DeepCopyInto(out *StuckLoopDetectionSpec) { *out = *in diff --git a/charts/foreman/templates/crds/agentictasks.yaml b/charts/foreman/templates/crds/agentictasks.yaml index 1b15875b..1bc02088 100644 --- a/charts/foreman/templates/crds/agentictasks.yaml +++ b/charts/foreman/templates/crds/agentictasks.yaml @@ -147,6 +147,8 @@ spec: - verify - review - freeform + - integrate + - reconcile type: string mcpEnabled: description: |- @@ -216,6 +218,12 @@ spec: - reset - rebase type: string + contract: + description: |- + Contract is the slice plan's prose contract, handed to a reconcile + task's LLM sweep as the description of the intended shared interfaces. + Reconcile only. Part of #1033. + type: string gateAdvisories: description: |- GateAdvisories holds non-blocking findings copied from the upstream @@ -286,6 +294,69 @@ spec: executor logs and falls back to branching from BaseBranch rather than failing. type: string + sharedIdentifiers: + description: |- + SharedIdentifiers pins the exact cross-slice strings a reconcile task + verifies against the integrated union. Reconcile only. Part of #1033. + items: + description: |- + SharedIdentifier pins one exact string that crosses a slice boundary (a + metric name, config key, or CRD field). The reconcile task asserts the ID + appears verbatim in the DefinedBy slice and every ReferencedBy slice; a + missing pin is an authoritative drift. Part of Sliced Workloads (#1033). + properties: + definedBy: + description: DefinedBy is the slice name that must produce + the identifier. + minLength: 1 + type: string + id: + description: ID is the exact string every listed slice must + contain verbatim. + minLength: 1 + type: string + referencedBy: + description: ReferencedBy are the slice names that must + consume the identifier. + items: + type: string + type: array + required: + - definedBy + - id + type: object + type: array + slices: + description: |- + Slices are the disjoint slices of a sliced Workload. The integrate task + unions their branches; the reconcile task checks pins against their + files. Set on integrate and reconcile tasks. Part of #1033. + items: + description: |- + SliceRef describes one disjoint slice of a sliced Workload. The integrate + task unions each slice's Branch; the reconcile task verifies pinned + identifiers appear in each slice's Files. Part of Sliced Workloads (#1033). + properties: + branch: + description: |- + Branch is the git ref the slice's coder pushed. Required for the + integrate task, which applies each slice branch's diff onto the base. + type: string + files: + description: |- + Files are the repo-relative paths this slice owns, so a reconcile task + looks for each pinned identifier in the right slice. + items: + type: string + type: array + name: + description: Name is the slice's name within the plan. + minLength: 1 + type: string + required: + - name + type: object + type: array type: object priority: description: |- diff --git a/charts/foreman/templates/crds/workloads.yaml b/charts/foreman/templates/crds/workloads.yaml index ebbda790..ad22c0b8 100644 --- a/charts/foreman/templates/crds/workloads.yaml +++ b/charts/foreman/templates/crds/workloads.yaml @@ -421,6 +421,8 @@ spec: - verify - review - freeform + - integrate + - reconcile type: string name: description: |- @@ -482,6 +484,12 @@ spec: - reset - rebase type: string + contract: + description: |- + Contract is the slice plan's prose contract, handed to a reconcile + task's LLM sweep as the description of the intended shared interfaces. + Reconcile only. Part of #1033. + type: string gateAdvisories: description: |- GateAdvisories holds non-blocking findings copied from the upstream @@ -553,6 +561,69 @@ spec: executor logs and falls back to branching from BaseBranch rather than failing. type: string + sharedIdentifiers: + description: |- + SharedIdentifiers pins the exact cross-slice strings a reconcile task + verifies against the integrated union. Reconcile only. Part of #1033. + items: + description: |- + SharedIdentifier pins one exact string that crosses a slice boundary (a + metric name, config key, or CRD field). The reconcile task asserts the ID + appears verbatim in the DefinedBy slice and every ReferencedBy slice; a + missing pin is an authoritative drift. Part of Sliced Workloads (#1033). + properties: + definedBy: + description: DefinedBy is the slice name that must + produce the identifier. + minLength: 1 + type: string + id: + description: ID is the exact string every listed slice + must contain verbatim. + minLength: 1 + type: string + referencedBy: + description: ReferencedBy are the slice names that + must consume the identifier. + items: + type: string + type: array + required: + - definedBy + - id + type: object + type: array + slices: + description: |- + Slices are the disjoint slices of a sliced Workload. The integrate task + unions their branches; the reconcile task checks pins against their + files. Set on integrate and reconcile tasks. Part of #1033. + items: + description: |- + SliceRef describes one disjoint slice of a sliced Workload. The integrate + task unions each slice's Branch; the reconcile task verifies pinned + identifiers appear in each slice's Files. Part of Sliced Workloads (#1033). + properties: + branch: + description: |- + Branch is the git ref the slice's coder pushed. Required for the + integrate task, which applies each slice branch's diff onto the base. + type: string + files: + description: |- + Files are the repo-relative paths this slice owns, so a reconcile task + looks for each pinned identifier in the right slice. + items: + type: string + type: array + name: + description: Name is the slice's name within the plan. + minLength: 1 + type: string + required: + - name + type: object + type: array type: object priority: description: |- diff --git a/config/crd/bases/foreman.llmkube.dev_agentictasks.yaml b/config/crd/bases/foreman.llmkube.dev_agentictasks.yaml index 50a48753..7599ecf2 100644 --- a/config/crd/bases/foreman.llmkube.dev_agentictasks.yaml +++ b/config/crd/bases/foreman.llmkube.dev_agentictasks.yaml @@ -146,6 +146,8 @@ spec: - verify - review - freeform + - integrate + - reconcile type: string mcpEnabled: description: |- @@ -215,6 +217,12 @@ spec: - reset - rebase type: string + contract: + description: |- + Contract is the slice plan's prose contract, handed to a reconcile + task's LLM sweep as the description of the intended shared interfaces. + Reconcile only. Part of #1033. + type: string gateAdvisories: description: |- GateAdvisories holds non-blocking findings copied from the upstream @@ -285,6 +293,69 @@ spec: executor logs and falls back to branching from BaseBranch rather than failing. type: string + sharedIdentifiers: + description: |- + SharedIdentifiers pins the exact cross-slice strings a reconcile task + verifies against the integrated union. Reconcile only. Part of #1033. + items: + description: |- + SharedIdentifier pins one exact string that crosses a slice boundary (a + metric name, config key, or CRD field). The reconcile task asserts the ID + appears verbatim in the DefinedBy slice and every ReferencedBy slice; a + missing pin is an authoritative drift. Part of Sliced Workloads (#1033). + properties: + definedBy: + description: DefinedBy is the slice name that must produce + the identifier. + minLength: 1 + type: string + id: + description: ID is the exact string every listed slice must + contain verbatim. + minLength: 1 + type: string + referencedBy: + description: ReferencedBy are the slice names that must + consume the identifier. + items: + type: string + type: array + required: + - definedBy + - id + type: object + type: array + slices: + description: |- + Slices are the disjoint slices of a sliced Workload. The integrate task + unions their branches; the reconcile task checks pins against their + files. Set on integrate and reconcile tasks. Part of #1033. + items: + description: |- + SliceRef describes one disjoint slice of a sliced Workload. The integrate + task unions each slice's Branch; the reconcile task verifies pinned + identifiers appear in each slice's Files. Part of Sliced Workloads (#1033). + properties: + branch: + description: |- + Branch is the git ref the slice's coder pushed. Required for the + integrate task, which applies each slice branch's diff onto the base. + type: string + files: + description: |- + Files are the repo-relative paths this slice owns, so a reconcile task + looks for each pinned identifier in the right slice. + items: + type: string + type: array + name: + description: Name is the slice's name within the plan. + minLength: 1 + type: string + required: + - name + type: object + type: array type: object priority: description: |- diff --git a/config/crd/bases/foreman.llmkube.dev_workloads.yaml b/config/crd/bases/foreman.llmkube.dev_workloads.yaml index 9951caa6..cdbf2b0e 100644 --- a/config/crd/bases/foreman.llmkube.dev_workloads.yaml +++ b/config/crd/bases/foreman.llmkube.dev_workloads.yaml @@ -420,6 +420,8 @@ spec: - verify - review - freeform + - integrate + - reconcile type: string name: description: |- @@ -481,6 +483,12 @@ spec: - reset - rebase type: string + contract: + description: |- + Contract is the slice plan's prose contract, handed to a reconcile + task's LLM sweep as the description of the intended shared interfaces. + Reconcile only. Part of #1033. + type: string gateAdvisories: description: |- GateAdvisories holds non-blocking findings copied from the upstream @@ -552,6 +560,69 @@ spec: executor logs and falls back to branching from BaseBranch rather than failing. type: string + sharedIdentifiers: + description: |- + SharedIdentifiers pins the exact cross-slice strings a reconcile task + verifies against the integrated union. Reconcile only. Part of #1033. + items: + description: |- + SharedIdentifier pins one exact string that crosses a slice boundary (a + metric name, config key, or CRD field). The reconcile task asserts the ID + appears verbatim in the DefinedBy slice and every ReferencedBy slice; a + missing pin is an authoritative drift. Part of Sliced Workloads (#1033). + properties: + definedBy: + description: DefinedBy is the slice name that must + produce the identifier. + minLength: 1 + type: string + id: + description: ID is the exact string every listed slice + must contain verbatim. + minLength: 1 + type: string + referencedBy: + description: ReferencedBy are the slice names that + must consume the identifier. + items: + type: string + type: array + required: + - definedBy + - id + type: object + type: array + slices: + description: |- + Slices are the disjoint slices of a sliced Workload. The integrate task + unions their branches; the reconcile task checks pins against their + files. Set on integrate and reconcile tasks. Part of #1033. + items: + description: |- + SliceRef describes one disjoint slice of a sliced Workload. The integrate + task unions each slice's Branch; the reconcile task verifies pinned + identifiers appear in each slice's Files. Part of Sliced Workloads (#1033). + properties: + branch: + description: |- + Branch is the git ref the slice's coder pushed. Required for the + integrate task, which applies each slice branch's diff onto the base. + type: string + files: + description: |- + Files are the repo-relative paths this slice owns, so a reconcile task + looks for each pinned identifier in the right slice. + items: + type: string + type: array + name: + description: Name is the slice's name within the plan. + minLength: 1 + type: string + required: + - name + type: object + type: array type: object priority: description: |-