Files
home-network-k3s-impl/platform/pm-agent/namespace.yaml
jakman85andClaude Opus 5 c5b5b8672a
gate / gate (pull_request) Failing after 1m51s
gate / conformance (pull_request) Has been skipped
feat(pm-agent): an in-cluster AI Program Manager that turns Gitea issues into pull requests
A service that reads this repository's Gitea issues, decides what to do about each
thread as the Program Manager persona, and runs one worker agent per assignment that
authors a change, runs the merge gate and opens a pull request. It reacts to issue
creation and to comments; the assignment and all correspondence travel through the
issue thread and the PR. It authenticates with an Anthropic subscription token. It
never pushes to main and never merges.

Three choices carry the design, with alternatives recorded in docs/plans/:
 - it POLLS rather than receiving webhooks (Gitea's webhook SSRF guard, lost
   deliveries, and no inbound reach needed at all);
 - it keeps NO state: the trigger is "the newest thing in this thread was not
   written by me", so the issue thread is the state — no PVC, no backup obligation;
 - the model holds NO tools: its reply is one JSON object this code validates, so
   nothing in the path executes text.

The boundary is platform/kyverno/admission/pm-agent-job-shape.yaml (Enforce,
failurePolicy: Fail), not the RBAC: Kubernetes places no `use` check on
serviceAccountName, so "may create Jobs" is "may run arbitrary code as the
namespace's most privileged identity". Six rules admit only the committed shape, and
the worker identity is bound to nothing with no token mounted, so there is nothing to
escalate to. 28/28 offline Kyverno assertions.

Nothing has run: the image has never been built or pushed, and both the image digest
and the two credentials are deliberate placeholders. See the handoff entry's
"What I did NOT verify".

Refs CLAUDE.md §1/§3/§6, R-SEC-02, R-GITOPS-02.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-05 22:28:10 -05:00

35 lines
2.0 KiB
YAML

---
# pm-agent — the Program Manager agent service: it reads Gitea issues, decides what to do
# about each thread, and runs one worker agent per assignment that authors a change, gates
# it and opens a pull request. Full design: platform/pm-agent/README.md and
# docs/plans/pm-agent-issue-to-pr.md.
#
# NO UI, NO INGRESS. There is deliberately no `gateway-access: shared` label and no
# HTTPRoute: nothing connects INTO this namespace. The dispatcher polls Gitea rather than
# receiving webhooks, precisely so that a namespace whose whole purpose is running
# model-authored code needs no inbound path at all (the reasoning is in the header of
# infra/agent/pm-dispatch.py). The operator's interface is the Gitea issue thread and
# `kubectl logs`.
#
# INTENTIONALLY NOT homelab.keithjackson.dev/app-namespace=true. This is PLATFORM plumbing —
# CI/delivery machinery, like `gitea-actions` and `conformance`, not a user-facing app. Every
# Kyverno app policy matches on that label (the generated default-deny baseline, the
# egress-class and auth-mode label requirements), so carrying it would demand an auth mode
# and an egress class for a workload that serves no traffic. The baseline those policies
# would have generated is therefore shipped EXPLICITLY here instead — see networkpolicy.yaml.
#
# PodSecurity `restricted` IS enforced, unlike in `gitea-actions` (which needs a privileged
# DinD sidecar and so cannot). Nothing here needs any privilege: both the dispatcher and
# every worker run as uid 65532 with no capabilities, no privilege escalation and a
# read-only root filesystem. Declaring it means the kubelet refuses a pod that drifts from
# that, in addition to the admission policy that constrains the worker Job's shape.
apiVersion: v1
kind: Namespace
metadata:
name: pm-agent
labels:
app.kubernetes.io/part-of: pm-agent
homelab.keithjackson.dev/ci: pm-agent
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest