Initial scaffold: API service, K8s operator, and CRDs

Forgebot is a K8s operator + API service for dispatching AI agent
jobs from git forge commands. Includes:

- CRDs: AgentPool, AgentTask, ProviderQueue, RepositoryBinding
- API server with webhook handler, task queue, and comment proxy
- Operator controllers for task scheduling and job management
- Gitea provider with webhook parsing and signature verification
- PostgreSQL database with auto-migration
- Woodpecker CI pipelines and multi-stage Dockerfiles
This commit is contained in:
2026-06-08 22:49:18 +10:00
parent fd1a4956ed
commit 49d514c050
46 changed files with 3139 additions and 0 deletions
@@ -0,0 +1,159 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: agentpools.forgebot.unkin.net
spec:
group: forgebot.unkin.net
names:
kind: AgentPool
listKind: AgentPoolList
plural: agentpools
singular: agentpool
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.model
name: Model
type: string
- jsonPath: .spec.maxConcurrent
name: Max
type: integer
- jsonPath: .status.activeJobs
name: Active
type: integer
name: v1alpha1
schema:
openAPIV3Schema:
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:
properties:
credentialSecretRef:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
endpoint:
type: string
image:
type: string
maxConcurrent:
type: integer
model:
type: string
resources:
description: ResourceRequirements describes the compute resource requirements.
properties:
claims:
description: |-
Claims lists the names of resources, defined in spec.resourceClaims,
that are used by this container.
This field depends on the
DynamicResourceAllocation feature gate.
This field is immutable. It can only be set for containers.
items:
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
properties:
name:
description: |-
Name must match the name of one entry in pod.spec.resourceClaims of
the Pod where this field is used. It makes that resource available
inside a container.
type: string
request:
description: |-
Request is the name chosen for a request in the referenced claim.
If empty, everything from the claim is made available, otherwise
only the result of this request.
type: string
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Limits describes the maximum amount of compute resources allowed.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Requests describes the minimum amount of compute resources required.
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
otherwise to an implementation-defined value. Requests cannot exceed Limits.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
type: object
serviceAccountName:
type: string
required:
- credentialSecretRef
- endpoint
- image
- maxConcurrent
- model
type: object
status:
properties:
activeJobs:
type: integer
lastActivity:
type: string
totalRun:
format: int64
type: integer
required:
- activeJobs
- totalRun
type: object
type: object
served: true
storage: true
subresources:
status: {}
@@ -0,0 +1,115 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: agenttasks.forgebot.unkin.net
spec:
group: forgebot.unkin.net
names:
kind: AgentTask
listKind: AgentTaskList
plural: agenttasks
singular: agenttask
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.command
name: Command
type: string
- jsonPath: .spec.repository
name: Repo
type: string
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
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:
properties:
command:
type: string
context:
properties:
author:
type: string
body:
type: string
commentID:
format: int64
type: integer
issueNumber:
type: integer
prNumber:
type: integer
required:
- author
type: object
extraTools:
items:
type: string
type: array
parentTaskRef:
type: string
poolRef:
type: string
ref:
type: string
repository:
type: string
skill:
type: string
required:
- command
- context
- poolRef
- ref
- repository
type: object
status:
properties:
childTasks:
items:
type: string
type: array
endTime:
format: date-time
type: string
jobName:
type: string
message:
type: string
phase:
type: string
startTime:
format: date-time
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
@@ -0,0 +1,92 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: providerqueues.forgebot.unkin.net
spec:
group: forgebot.unkin.net
names:
kind: ProviderQueue
listKind: ProviderQueueList
plural: providerqueues
singular: providerqueue
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.provider
name: Provider
type: string
- jsonPath: .spec.pollInterval
name: Interval
type: string
name: v1alpha1
schema:
openAPIV3Schema:
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:
properties:
credentialSecretRef:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
endpoint:
type: string
pollInterval:
type: string
provider:
type: string
required:
- credentialSecretRef
- endpoint
- pollInterval
- provider
type: object
status:
properties:
lastError:
type: string
lastPoll:
format: date-time
type: string
tasksCreated:
format: int64
type: integer
required:
- tasksCreated
type: object
type: object
served: true
storage: true
subresources:
status: {}
@@ -0,0 +1,94 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: repositorybindings.forgebot.unkin.net
spec:
group: forgebot.unkin.net
names:
kind: RepositoryBinding
listKind: RepositoryBindingList
plural: repositorybindings
singular: repositorybinding
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.repository
name: Repo
type: string
- jsonPath: .spec.agentPoolRef
name: Pool
type: string
- jsonPath: .status.webhookRegistered
name: Webhook
type: boolean
name: v1alpha1
schema:
openAPIV3Schema:
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:
properties:
agentPoolRef:
type: string
allowedCommands:
items:
type: string
type: array
allowedUsers:
items:
type: string
type: array
providerQueueRef:
type: string
repository:
type: string
skillMapping:
items:
properties:
command:
type: string
skill:
type: string
required:
- command
- skill
type: object
type: array
required:
- agentPoolRef
- providerQueueRef
- repository
type: object
status:
properties:
lastError:
type: string
webhookRegistered:
type: boolean
required:
- webhookRegistered
type: object
type: object
served: true
storage: true
subresources:
status: {}
+21
View File
@@ -0,0 +1,21 @@
---
apiVersion: forgebot.unkin.net/v1alpha1
kind: AgentPool
metadata:
name: default-pool
namespace: forgebot
spec:
model: claude-sonnet-4-20250514
endpoint: https://litellm.k8s.syd1.au.unkin.net
maxConcurrent: 3
image: git.unkin.net/unkin/agent-dev:latest
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi
credentialSecretRef:
name: litellm-api-key
serviceAccountName: forgebot-agent
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: forgebot.unkin.net/v1alpha1
kind: AgentTask
metadata:
name: example-review
namespace: forgebot
spec:
poolRef: default-pool
command: review
skill: review
repository: unkin/artifactapi
ref: refs/pull/42/head
context:
prNumber: 42
commentID: 12345
body: "/review check for security issues"
author: unkinben
+12
View File
@@ -0,0 +1,12 @@
---
apiVersion: forgebot.unkin.net/v1alpha1
kind: ProviderQueue
metadata:
name: gitea-queue
namespace: forgebot
spec:
provider: gitea
endpoint: http://forgebot-api.forgebot.svc.cluster.local/api/v1
pollInterval: 30s
credentialSecretRef:
name: forgebot-api-token
+29
View File
@@ -0,0 +1,29 @@
---
apiVersion: forgebot.unkin.net/v1alpha1
kind: RepositoryBinding
metadata:
name: artifactapi
namespace: forgebot
spec:
repository: unkin/artifactapi
providerQueueRef: gitea-queue
agentPoolRef: default-pool
allowedUsers:
- unkinben
allowedCommands:
- plan
- review
- implement
- test
- fix
skillMapping:
- command: plan
skill: plan
- command: review
skill: review
- command: implement
skill: implement
- command: test
skill: test
- command: fix
skill: fix