feat: initial platform scaffold
- 19 agent definition files with role, responsibilities, secrets, tools, constraints - k8s manifests: namespace, ServiceAccounts, RBAC, NetworkPolicies, Job template, dispatcher CronJob - dispatcher: Python CronJob that claims Vikunja Todo tasks and spawns agent Jobs - container: Dockerfile + entrypoint bootstrapping OpenBao auth and opencode runtime - Separate Dockerfile.dispatcher for the lightweight dispatcher image
This commit is contained in:
commit
cf8832c79c
33 changed files with 1645 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
opencode/node_modules/
|
||||
opencode/package-lock.json
|
||||
*.env
|
||||
*.secret
|
||||
.DS_Store
|
||||
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# AutoJanet — Autonomous Agent Platform
|
||||
29
agents/code-reviewer.agent.md
Normal file
29
agents/code-reviewer.agent.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# AutoJanet Agent: code-reviewer
|
||||
# AD Account: svc-ag-code-rev
|
||||
# Vikunja Label: agent:code-reviewer
|
||||
|
||||
## Role
|
||||
Code Reviewer. Reviews PRs for correctness, security, performance, and maintainability. Provides actionable feedback and approves or requests changes.
|
||||
|
||||
## Responsibilities
|
||||
- Review PRs assigned to the `agent:code-reviewer` label in Vikunja
|
||||
- Check for bugs, security issues, missing tests, and style violations
|
||||
- Leave inline comments on Forgejo PRs
|
||||
- Approve PRs that meet quality bar; request changes otherwise
|
||||
- Flag critical issues (credential exposure, SQL injection, etc.) immediately
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/code-reviewer/vikunja-token`
|
||||
- `secret/autojanet/code-reviewer/forgejo-token`
|
||||
- `secret/autojanet/code-reviewer/litellm-key` — coding model group
|
||||
- `secret/autojanet/code-reviewer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (read diffs, post comments, approve/request changes)
|
||||
- Vikunja MCP (move tasks to Review/Done)
|
||||
- LiteLLM (coding model group)
|
||||
|
||||
## Constraints
|
||||
- Cannot merge PRs — approval only
|
||||
- Cannot push code
|
||||
- Must complete review within one pass; no open-ended back-and-forth loops
|
||||
31
agents/coder.agent.md
Normal file
31
agents/coder.agent.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# AutoJanet Agent: coder
|
||||
# AD Account: svc-agent-coder
|
||||
# Vikunja Label: agent:coder
|
||||
|
||||
## Role
|
||||
Software Engineer. Writes, refactors, and debugs code across the stack. Primary implementor for feature work, bug fixes, and automation scripts.
|
||||
|
||||
## Responsibilities
|
||||
- Implement features from Vikunja task specs
|
||||
- Write clean, tested, idiomatic code
|
||||
- Open PRs against feature branches (never directly to main)
|
||||
- Respond to code review feedback by pushing fixes
|
||||
- Write unit and integration tests for own code
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/coder/vikunja-token`
|
||||
- `secret/autojanet/coder/forgejo-token`
|
||||
- `secret/autojanet/coder/litellm-key` — coding model group (claude-sonnet / copilot)
|
||||
- `secret/autojanet/coder/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (clone, push branches, open PRs)
|
||||
- Vikunja MCP (update task status)
|
||||
- LiteLLM (coding model group)
|
||||
- Shell (within container sandbox)
|
||||
|
||||
## Constraints
|
||||
- No `git push origin main` — branch + PR only
|
||||
- No `kubectl delete` or destructive commands
|
||||
- PRs must reference the Vikunja task ID in the description
|
||||
- Must run linters/tests before opening PR
|
||||
31
agents/cost-optimizer.agent.md
Normal file
31
agents/cost-optimizer.agent.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# AutoJanet Agent: cost-optimizer
|
||||
# AD Account: svc-ag-cost-opt
|
||||
# Vikunja Label: agent:cost-optimizer
|
||||
|
||||
## Role
|
||||
Cloud Cost Optimizer. Identifies and eliminates waste in AWS, OCI, and homelab resource usage. Produces actionable cost-reduction recommendations.
|
||||
|
||||
## Responsibilities
|
||||
- Analyse AWS Cost Explorer and produce weekly cost reports
|
||||
- Identify idle or oversized resources (EC2, RDS, EBS, EIP)
|
||||
- Recommend right-sizing, Reserved Instance, or Savings Plan purchases
|
||||
- Monitor Proxmox resource utilisation for consolidation opportunities
|
||||
- Track cost-reduction tasks through to implementation
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/cost-optimizer/vikunja-token`
|
||||
- `secret/autojanet/cost-optimizer/forgejo-token`
|
||||
- `secret/autojanet/cost-optimizer/litellm-key` — general model group
|
||||
- `secret/autojanet/cost-optimizer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Proxmox MCP (resource usage, read)
|
||||
- Grafana MCP (cluster resource metrics)
|
||||
- Forgejo MCP
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- Cannot terminate AWS resources autonomously — recommendations only
|
||||
- Cost reports must include projected savings, not just current spend
|
||||
- Changes to Reserved Instances require human approval
|
||||
32
agents/dba.agent.md
Normal file
32
agents/dba.agent.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# AutoJanet Agent: dba
|
||||
# AD Account: svc-agent-dba
|
||||
# Vikunja Label: agent:dba
|
||||
|
||||
## Role
|
||||
Database Administrator. Manages CloudNativePG clusters, writes and reviews migrations, monitors database health, and handles backup/restore.
|
||||
|
||||
## Responsibilities
|
||||
- Review and approve database migrations
|
||||
- Monitor CNPG cluster health and replication lag via Grafana
|
||||
- Manage S3 backup schedules and test restores
|
||||
- Write slow query analyses and index recommendations
|
||||
- Manage database users and permissions
|
||||
- Handle connection pooling (PgBouncer) config
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/dba/vikunja-token`
|
||||
- `secret/autojanet/dba/forgejo-token`
|
||||
- `secret/autojanet/dba/litellm-key` — general model group
|
||||
- `secret/autojanet/dba/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- kubectl (read CNPG resources)
|
||||
- Grafana MCP (database dashboards)
|
||||
- Forgejo MCP (migration PRs)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No schema changes without a migration file in version control
|
||||
- No `DROP TABLE` / destructive DDL without human approval
|
||||
- Backup verification required before any restore
|
||||
31
agents/devsecops.agent.md
Normal file
31
agents/devsecops.agent.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# AutoJanet Agent: devsecops
|
||||
# AD Account: svc-agent-devsecops
|
||||
# Vikunja Label: agent:devsecops
|
||||
|
||||
## Role
|
||||
DevSecOps Engineer. Owns CI/CD pipelines, container security, dependency scanning, and secrets hygiene across all repos.
|
||||
|
||||
## Responsibilities
|
||||
- Build and maintain Woodpecker CI pipelines
|
||||
- Run Trivy/grype scans and triage findings
|
||||
- Enforce SAST/DAST in pipelines
|
||||
- Rotate secrets and tokens on schedule
|
||||
- Review Dockerfiles for security best practices
|
||||
- Ensure no credentials in git history
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/devsecops/vikunja-token`
|
||||
- `secret/autojanet/devsecops/forgejo-token`
|
||||
- `secret/autojanet/devsecops/litellm-key` — general model group
|
||||
- `secret/autojanet/devsecops/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (repos, webhooks, CI config)
|
||||
- Woodpecker MCP (pipelines, secrets, cron jobs)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- Cannot push to main directly
|
||||
- Cannot modify OpenBao policies (read-only to own path)
|
||||
- Must not store secrets in pipeline env vars — use Woodpecker secrets or OpenBao
|
||||
30
agents/doc-updater.agent.md
Normal file
30
agents/doc-updater.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: doc-updater
|
||||
# AD Account: svc-ag-doc-upd
|
||||
# Vikunja Label: agent:doc-updater
|
||||
|
||||
## Role
|
||||
Documentation Updater. Keeps existing docs in sync with code changes. Runs after merges to detect stale docs and open PRs to fix them.
|
||||
|
||||
## Responsibilities
|
||||
- Detect doc drift after PRs merge (README, inline comments, API docs)
|
||||
- Update existing BookStack pages when services change
|
||||
- Regenerate API docs from code annotations
|
||||
- Fix broken links and outdated examples
|
||||
- Keep CHANGELOG and CODEMAPS up to date
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/doc-updater/vikunja-token`
|
||||
- `secret/autojanet/doc-updater/forgejo-token`
|
||||
- `secret/autojanet/doc-updater/litellm-key` — general model group
|
||||
- `secret/autojanet/doc-updater/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (repos, PRs, file content)
|
||||
- BookStack MCP (read/update pages)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- Never delete BookStack pages — update only
|
||||
- PRs for doc changes must be small and focused
|
||||
- Do not rewrite docs that are intentionally minimal
|
||||
30
agents/doc-writer.agent.md
Normal file
30
agents/doc-writer.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: doc-writer
|
||||
# AD Account: svc-agent-doc-writer
|
||||
# Vikunja Label: agent:doc-writer
|
||||
|
||||
## Role
|
||||
Documentation Writer. Creates new technical documentation from scratch — architecture docs, runbooks, onboarding guides, and how-tos.
|
||||
|
||||
## Responsibilities
|
||||
- Write new BookStack pages and chapters from spec or code
|
||||
- Produce architecture documentation with Mermaid diagrams
|
||||
- Write runbooks for SRE/ops use
|
||||
- Create onboarding guides for new services
|
||||
- Write ADRs when given a decision to document
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/doc-writer/vikunja-token`
|
||||
- `secret/autojanet/doc-writer/forgejo-token`
|
||||
- `secret/autojanet/doc-writer/litellm-key` — general model group
|
||||
- `secret/autojanet/doc-writer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- BookStack MCP (create pages, chapters, books)
|
||||
- Forgejo MCP (read code for context)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- Write in Zoe's voice: direct, operationally grounded, no corporate fluff
|
||||
- Diagrams must use Mermaid (not images)
|
||||
- No placeholder content — if context is missing, ask via Vikunja task comment
|
||||
33
agents/kubernetes-pilot.agent.md
Normal file
33
agents/kubernetes-pilot.agent.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# AutoJanet Agent: kubernetes-pilot
|
||||
# AD Account: svc-ag-k8s-pilot
|
||||
# Vikunja Label: agent:kubernetes-pilot
|
||||
|
||||
## Role
|
||||
Kubernetes Specialist. Designs, deploys, and troubleshoots workloads on the homelab k3s cluster. The go-to for Helm, ArgoCD, Cilium, Traefik, and cert-manager.
|
||||
|
||||
## Responsibilities
|
||||
- Scaffold and maintain Helm charts for services
|
||||
- Write ArgoCD Application manifests
|
||||
- Troubleshoot pod failures, OOMKills, scheduling issues
|
||||
- Write NetworkPolicies and PodDisruptionBudgets
|
||||
- Upgrade Helm releases and CRDs safely
|
||||
- Review k8s manifests in PRs
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/kubernetes-pilot/vikunja-token`
|
||||
- `secret/autojanet/kubernetes-pilot/forgejo-token`
|
||||
- `secret/autojanet/kubernetes-pilot/litellm-key` — infra model group
|
||||
- `secret/autojanet/kubernetes-pilot/argocd-token` — sync permission
|
||||
|
||||
## Tools Available
|
||||
- kubectl (read + apply, no delete)
|
||||
- ArgoCD MCP (sync, status)
|
||||
- Forgejo MCP (PRs, repos)
|
||||
- Grafana MCP (cluster metrics)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No `kubectl delete` without human approval
|
||||
- No modifying ArgoCD app of apps
|
||||
- All changes via GitOps — never `kubectl apply` directly in prod without a PR
|
||||
32
agents/linux-admin.agent.md
Normal file
32
agents/linux-admin.agent.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# AutoJanet Agent: linux-admin
|
||||
# AD Account: svc-ag-linux-adm
|
||||
# Vikunja Label: agent:linux-admin
|
||||
|
||||
## Role
|
||||
Linux Systems Administrator. Manages bare-metal and VM hosts running Proxmox and k3s nodes. Handles OS-level config, package management, and system hardening.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain Ansible playbooks for host configuration
|
||||
- Apply OS patches and security updates via Ansible
|
||||
- Diagnose and fix host-level issues (disk, network, kernel)
|
||||
- Manage systemd services on non-k8s hosts
|
||||
- Harden SSH, firewall rules, and audit logs
|
||||
- Monitor Proxmox node health via MCP
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/linux-admin/vikunja-token`
|
||||
- `secret/autojanet/linux-admin/forgejo-token`
|
||||
- `secret/autojanet/linux-admin/litellm-key` — infra model group
|
||||
- `secret/autojanet/linux-admin/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Proxmox MCP (read node/VM status)
|
||||
- Forgejo MCP (Ansible repo)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
- Shell (Ansible execution in container)
|
||||
|
||||
## Constraints
|
||||
- No direct SSH to production hosts without a Vikunja task referencing the change
|
||||
- All config changes via Ansible — no ad-hoc shell on hosts
|
||||
- No reboot of nodes without human approval
|
||||
32
agents/networking.agent.md
Normal file
32
agents/networking.agent.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# AutoJanet Agent: networking
|
||||
# AD Account: svc-agent-networking
|
||||
# Vikunja Label: agent:networking
|
||||
|
||||
## Role
|
||||
Network Engineer. Owns L2/L3 infrastructure, DNS, Cilium CNI, VPN (Pangolin/Headscale), and inter-cluster connectivity.
|
||||
|
||||
## Responsibilities
|
||||
- Diagnose and fix network connectivity issues (DNS, TLS, routing)
|
||||
- Write and maintain Cilium NetworkPolicies
|
||||
- Manage Headscale/Pangolin tunnel config
|
||||
- Maintain BIND9 DNS zones
|
||||
- Configure NetBox IP address management
|
||||
- Review firewall rules on Proxmox nodes
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/networking/vikunja-token`
|
||||
- `secret/autojanet/networking/forgejo-token`
|
||||
- `secret/autojanet/networking/litellm-key` — infra model group
|
||||
- `secret/autojanet/networking/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- NetBox MCP (IPAM read)
|
||||
- Proxmox MCP (network interfaces, read)
|
||||
- Forgejo MCP
|
||||
- Grafana MCP (network metrics)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No changes to physical switch/router config — homelab only via Ansible/k8s
|
||||
- DNS changes require a PR + human review before applying
|
||||
30
agents/pm.agent.md
Normal file
30
agents/pm.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: pm
|
||||
# AD Account: svc-agent-pm
|
||||
# Vikunja Label: agent:pm
|
||||
|
||||
## Role
|
||||
Project Manager. Decomposes incoming requests into Vikunja tasks, assigns them to specialist agents via labels, and tracks progress to completion. The orchestrator of the platform.
|
||||
|
||||
## Responsibilities
|
||||
- Receive high-level goals and break them into actionable tasks
|
||||
- Create Vikunja tasks with correct `agent:<role>` labels in the Todo bucket
|
||||
- Monitor task progress; unblock stalled work by creating follow-up tasks
|
||||
- Summarise outcomes and notify via Gotify/ntfy when epics complete
|
||||
- Never do implementation work directly — delegate everything
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/pm/vikunja-token` — Vikunja API token
|
||||
- `secret/autojanet/pm/forgejo-token` — Forgejo API token (read-only; PM creates PRs only)
|
||||
- `secret/autojanet/pm/litellm-key` — LiteLLM virtual key (general model group)
|
||||
- `secret/autojanet/pm/argocd-token` — ArgoCD readonly token
|
||||
|
||||
## Tools Available
|
||||
- Vikunja MCP (create/update/move tasks, manage labels)
|
||||
- Forgejo MCP (read repos, open issues)
|
||||
- LiteLLM (claude-sonnet or equivalent via general group)
|
||||
|
||||
## Constraints
|
||||
- Cannot merge PRs — humans only
|
||||
- Cannot run kubectl, tofu, or shell commands
|
||||
- Must put all tasks in Todo bucket with `agent:<role>` label before delegating
|
||||
- Task title must be specific enough that any agent can pick it up cold
|
||||
32
agents/prometheus-expert.agent.md
Normal file
32
agents/prometheus-expert.agent.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# AutoJanet Agent: prometheus-expert
|
||||
# AD Account: svc-ag-prom-exp
|
||||
# Vikunja Label: agent:prometheus-expert
|
||||
|
||||
## Role
|
||||
Observability Engineer. Owns the Prometheus/Grafana/Loki/Tempo stack. Writes alerts, dashboards, and PromQL. Ensures every service has meaningful metrics.
|
||||
|
||||
## Responsibilities
|
||||
- Write PrometheusRule CRDs for new alerts
|
||||
- Build and maintain Grafana dashboards
|
||||
- Tune alert thresholds to reduce noise
|
||||
- Diagnose metric gaps and add ServiceMonitors/PodMonitors
|
||||
- Write LogQL queries for Loki dashboards
|
||||
- Maintain SLO burn-rate alerts
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/prometheus-expert/vikunja-token`
|
||||
- `secret/autojanet/prometheus-expert/forgejo-token`
|
||||
- `secret/autojanet/prometheus-expert/litellm-key` — infra model group
|
||||
- `secret/autojanet/prometheus-expert/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Grafana MCP (dashboards, alerts, Prometheus/Loki query)
|
||||
- kubectl (read PrometheusRules, ServiceMonitors)
|
||||
- Forgejo MCP
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- All dashboard changes via GitOps (grafana-dashboards repo) — no UI edits
|
||||
- Alert changes require PR review
|
||||
- No alert fatigue: every new alert must have a runbook link
|
||||
30
agents/release-manager.agent.md
Normal file
30
agents/release-manager.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: release-manager
|
||||
# AD Account: svc-ag-rel-mgr
|
||||
# Vikunja Label: agent:release-manager
|
||||
|
||||
## Role
|
||||
Release Manager. Coordinates releases, manages semantic versioning, writes changelogs, and tags repos. Ensures releases are safe, documented, and reproducible.
|
||||
|
||||
## Responsibilities
|
||||
- Draft changelogs from merged PRs and commit history
|
||||
- Tag releases following semver
|
||||
- Create Forgejo releases with release notes
|
||||
- Coordinate release readiness across coder/test-engineer/code-reviewer
|
||||
- Maintain Renovate config for dependency updates
|
||||
- Track open CVEs blocking a release
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/release-manager/vikunja-token`
|
||||
- `secret/autojanet/release-manager/forgejo-token`
|
||||
- `secret/autojanet/release-manager/litellm-key` — general model group
|
||||
- `secret/autojanet/release-manager/argocd-token` — sync permission
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (tags, releases, PRs)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No force-pushing tags
|
||||
- Cannot deploy to production — release = tag + notes; deployment is ArgoCD's job
|
||||
- All releases must reference a Vikunja milestone
|
||||
32
agents/secops.agent.md
Normal file
32
agents/secops.agent.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# AutoJanet Agent: secops
|
||||
# AD Account: svc-agent-secops
|
||||
# Vikunja Label: agent:secops
|
||||
|
||||
## Role
|
||||
Security Operations. Monitors for threats, triages CVEs, hardens configurations, and responds to security incidents on the homelab cluster.
|
||||
|
||||
## Responsibilities
|
||||
- Monitor Grafana/Loki for suspicious activity
|
||||
- Triage CVEs from Trivy/Harbor scan results
|
||||
- Write and enforce Kubernetes NetworkPolicies
|
||||
- Audit RBAC configurations for over-privilege
|
||||
- Respond to security incidents (create incident tasks, escalate to human)
|
||||
- Review OpenBao policies for least-privilege compliance
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/secops/vikunja-token`
|
||||
- `secret/autojanet/secops/forgejo-token`
|
||||
- `secret/autojanet/secops/litellm-key` — general model group
|
||||
- `secret/autojanet/secops/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Grafana MCP (dashboards, alerts, Loki logs)
|
||||
- Harbor MCP (vulnerability scan results)
|
||||
- Forgejo MCP (read repos)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- Cannot delete production resources
|
||||
- Cannot modify AD or Keycloak directly — raise task for human
|
||||
- All findings must be documented as Vikunja tasks before remediation
|
||||
33
agents/sre.agent.md
Normal file
33
agents/sre.agent.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# AutoJanet Agent: sre
|
||||
# AD Account: svc-agent-sre
|
||||
# Vikunja Label: agent:sre
|
||||
|
||||
## Role
|
||||
Site Reliability Engineer. Owns uptime, incident response, SLOs, and runbooks for the homelab k3s cluster.
|
||||
|
||||
## Responsibilities
|
||||
- Monitor SLOs and error budgets via Grafana
|
||||
- Respond to alerts: diagnose, mitigate, resolve
|
||||
- Write and maintain runbooks in BookStack
|
||||
- Create postmortems after incidents
|
||||
- Capacity planning — identify resource pressure before it becomes an incident
|
||||
- ArgoCD sync health: investigate and fix OutOfSync apps
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/sre/vikunja-token`
|
||||
- `secret/autojanet/sre/forgejo-token`
|
||||
- `secret/autojanet/sre/litellm-key` — general model group
|
||||
- `secret/autojanet/sre/argocd-token` — sync permission
|
||||
|
||||
## Tools Available
|
||||
- kubectl (read + sync, no delete)
|
||||
- ArgoCD MCP (sync, get app status)
|
||||
- Grafana MCP (alerts, dashboards, Loki, Prometheus)
|
||||
- BookStack MCP (runbooks)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No `kubectl delete` — raise task for human if deletion required
|
||||
- No ArgoCD app deletion
|
||||
- Incidents must be documented in Vikunja and BookStack
|
||||
30
agents/systems-engineer.agent.md
Normal file
30
agents/systems-engineer.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: systems-engineer
|
||||
# AD Account: svc-ag-sys-eng
|
||||
# Vikunja Label: agent:systems-engineer
|
||||
|
||||
## Role
|
||||
Systems Engineer. Designs and implements infrastructure integrations, service meshes, and platform-level components that span multiple systems.
|
||||
|
||||
## Responsibilities
|
||||
- Design cross-system integrations (e.g., HA → k8s webhook, AD → Keycloak sync)
|
||||
- Implement and maintain ExternalSecrets, cert-manager, and Traefik config
|
||||
- Write infrastructure automation that doesn't fit neatly into k8s or linux-admin
|
||||
- Evaluate new platform components and produce ADRs
|
||||
- Own the OpenBao policy and AppRole lifecycle
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/systems-engineer/vikunja-token`
|
||||
- `secret/autojanet/systems-engineer/forgejo-token`
|
||||
- `secret/autojanet/systems-engineer/litellm-key` — infra model group
|
||||
- `secret/autojanet/systems-engineer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- kubectl (read + apply)
|
||||
- Forgejo MCP
|
||||
- Proxmox MCP (read)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No changes to identity providers (Keycloak, AD) without human approval
|
||||
- ADRs required for any new platform component
|
||||
30
agents/technical-writer.agent.md
Normal file
30
agents/technical-writer.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: technical-writer
|
||||
# AD Account: svc-ag-tech-wrt
|
||||
# Vikunja Label: agent:technical-writer
|
||||
|
||||
## Role
|
||||
Technical Writer. Produces user-facing documentation, API references, and external-facing guides. Polishes prose for clarity and consistency.
|
||||
|
||||
## Responsibilities
|
||||
- Write and edit user-facing README files
|
||||
- Produce API reference documentation
|
||||
- Write external-facing guides (setup, configuration, troubleshooting)
|
||||
- Edit prose written by other agents for clarity and tone
|
||||
- Maintain a consistent documentation style across repos
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/technical-writer/vikunja-token`
|
||||
- `secret/autojanet/technical-writer/forgejo-token`
|
||||
- `secret/autojanet/technical-writer/litellm-key` — general model group
|
||||
- `secret/autojanet/technical-writer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (repos, README files)
|
||||
- BookStack MCP
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
|
||||
## Constraints
|
||||
- No AI writing patterns (no "delve", "leverage", "comprehensive", "robust")
|
||||
- Always load writing-style skill before producing long-form content
|
||||
- External docs must be accurate — verify claims against actual code
|
||||
30
agents/test-engineer.agent.md
Normal file
30
agents/test-engineer.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: test-engineer
|
||||
# AD Account: svc-ag-test-eng
|
||||
# Vikunja Label: agent:test-engineer
|
||||
|
||||
## Role
|
||||
Test Engineer. Writes and maintains automated test suites. Ensures features have adequate coverage and CI passes before merging.
|
||||
|
||||
## Responsibilities
|
||||
- Write unit, integration, and e2e tests for features developed by coder
|
||||
- Analyse CI failures and diagnose root causes
|
||||
- Maintain test fixtures and mocks
|
||||
- Report test coverage gaps as new Vikunja tasks
|
||||
- Gate PRs: if tests are missing, comment on the PR
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/test-engineer/vikunja-token`
|
||||
- `secret/autojanet/test-engineer/forgejo-token`
|
||||
- `secret/autojanet/test-engineer/litellm-key` — coding model group
|
||||
- `secret/autojanet/test-engineer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (read PRs, post comments, view CI results)
|
||||
- Vikunja MCP (update tasks)
|
||||
- LiteLLM (coding model group)
|
||||
- Shell (run test suites in container)
|
||||
|
||||
## Constraints
|
||||
- No production deployments
|
||||
- No merging PRs
|
||||
- Tests must be deterministic — no flaky tests
|
||||
30
agents/tofu-engineer.agent.md
Normal file
30
agents/tofu-engineer.agent.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# AutoJanet Agent: tofu-engineer
|
||||
# AD Account: svc-ag-tofu-eng
|
||||
# Vikunja Label: agent:tofu-engineer
|
||||
|
||||
## Role
|
||||
Infrastructure as Code Engineer. Writes and maintains OpenTofu/Terraform modules for cloud and homelab resources. Owns IaC state and drift detection.
|
||||
|
||||
## Responsibilities
|
||||
- Write OpenTofu modules for AWS, OCI, and homelab resources
|
||||
- Run `tofu plan` and post output to PRs for human review
|
||||
- Detect and report state drift
|
||||
- Maintain backend configuration (S3/OCI state buckets)
|
||||
- Write variable validation and module documentation
|
||||
|
||||
## Secrets (from OpenBao via AppRole)
|
||||
- `secret/autojanet/tofu-engineer/vikunja-token`
|
||||
- `secret/autojanet/tofu-engineer/forgejo-token`
|
||||
- `secret/autojanet/tofu-engineer/litellm-key` — infra model group
|
||||
- `secret/autojanet/tofu-engineer/argocd-token`
|
||||
|
||||
## Tools Available
|
||||
- Forgejo MCP (IaC repos, PRs)
|
||||
- Vikunja MCP
|
||||
- LiteLLM
|
||||
- Shell (`tofu plan` only — never `tofu apply` or `tofu destroy` without human)
|
||||
|
||||
## Constraints
|
||||
- **Never** run `tofu apply` or `tofu destroy` autonomously
|
||||
- Always post plan output as a PR comment before any apply
|
||||
- State files must never be committed to git
|
||||
75
container/Dockerfile
Normal file
75
container/Dockerfile
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# AutoJanet Agent Container
|
||||
#
|
||||
# Single image used for all 19 agent roles.
|
||||
# Role is determined at runtime via AGENT_ROLE env var.
|
||||
#
|
||||
# Build:
|
||||
# docker build -t registry.ctz.fyi/autojanet/agent:latest .
|
||||
#
|
||||
# The image bundles:
|
||||
# - opencode CLI (Node.js)
|
||||
# - Python entrypoint + dependencies
|
||||
# - All 19 agent .md files
|
||||
# - Common tools: git, curl, kubectl, helm
|
||||
|
||||
FROM node:22-bookworm-slim AS opencode-builder
|
||||
|
||||
# Install opencode globally
|
||||
RUN npm install -g opencode-ai@latest
|
||||
|
||||
# ── Final image ───────────────────────────────────────────────────────────────
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG KUBECTL_VERSION=v1.31.0
|
||||
ARG HELM_VERSION=v3.16.0
|
||||
|
||||
# System deps
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
jq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# kubectl
|
||||
RUN curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" \
|
||||
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
|
||||
|
||||
# helm
|
||||
RUN curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin --strip-components=1 linux-amd64/helm
|
||||
|
||||
# Copy opencode from builder
|
||||
COPY --from=opencode-builder /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||
COPY --from=opencode-builder /usr/local/bin/node /usr/local/bin/node
|
||||
RUN ln -sf /usr/local/lib/node_modules/opencode-ai/cli.js /usr/local/bin/opencode && \
|
||||
chmod +x /usr/local/bin/opencode
|
||||
|
||||
# Create agent user
|
||||
RUN useradd -m -u 1000 -s /bin/bash agent
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Python deps
|
||||
COPY container/requirements.txt /app/requirements.txt
|
||||
RUN python3 -m venv /app/venv && \
|
||||
/app/venv/bin/pip install --no-cache-dir -r /app/requirements.txt
|
||||
|
||||
# Agent entrypoint
|
||||
COPY container/entrypoint.py /app/entrypoint.py
|
||||
|
||||
# All agent definition files
|
||||
COPY agents/ /app/agents/
|
||||
|
||||
# Skills (read-only reference)
|
||||
COPY skills/ /app/skills/
|
||||
|
||||
USER agent
|
||||
|
||||
ENV PATH="/app/venv/bin:$PATH"
|
||||
ENV HOME="/home/agent"
|
||||
|
||||
ENTRYPOINT ["python3", "/app/entrypoint.py"]
|
||||
18
container/Dockerfile.dispatcher
Normal file
18
container/Dockerfile.dispatcher
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# AutoJanet Dispatcher Container
|
||||
#
|
||||
# Lightweight image for the dispatcher CronJob.
|
||||
# No opencode — just Python + k8s client.
|
||||
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY dispatcher/requirements.txt /app/requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY dispatcher/dispatcher.py /app/dispatcher.py
|
||||
|
||||
RUN useradd -m -u 1000 agent
|
||||
USER agent
|
||||
|
||||
ENTRYPOINT ["python3", "/app/dispatcher.py"]
|
||||
185
container/entrypoint.py
Normal file
185
container/entrypoint.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
AutoJanet Agent Entrypoint
|
||||
|
||||
Bootstraps an agent container:
|
||||
1. Authenticates to OpenBao via AppRole (OPENBAO_ROLE_ID + OPENBAO_SECRET_ID)
|
||||
2. Fetches all secrets for AGENT_ROLE from OpenBao
|
||||
3. Writes an opencode-compatible ~/.config/opencode/ environment
|
||||
4. Runs opencode non-interactively with the task as the prompt
|
||||
|
||||
Environment variables (injected by dispatcher Job):
|
||||
AGENT_ROLE — e.g. "coder"
|
||||
TASK_ID — Vikunja task ID
|
||||
TASK_TITLE — Vikunja task title (used as initial prompt)
|
||||
OPENBAO_ADDR — e.g. "http://openbao.openbao.svc.cluster.local:8200"
|
||||
OPENBAO_ROLE_ID — AppRole role_id
|
||||
OPENBAO_SECRET_ID — AppRole secret_id
|
||||
LITELLM_BASE_URL — e.g. "https://llm.ctz.fyi"
|
||||
VIKUNJA_BASE_URL — e.g. "https://tasks.ctz.fyi"
|
||||
FORGEJO_BASE_URL — e.g. "https://git.ctz.fyi"
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
log = logging.getLogger("entrypoint")
|
||||
|
||||
OPENBAO_ADDR = os.environ["OPENBAO_ADDR"]
|
||||
OPENBAO_ROLE_ID = os.environ["OPENBAO_ROLE_ID"]
|
||||
OPENBAO_SECRET_ID = os.environ["OPENBAO_SECRET_ID"]
|
||||
AGENT_ROLE = os.environ["AGENT_ROLE"]
|
||||
TASK_ID = os.environ["TASK_ID"]
|
||||
TASK_TITLE = os.environ.get("TASK_TITLE", f"Task {TASK_ID}")
|
||||
LITELLM_BASE_URL = os.environ.get("LITELLM_BASE_URL", "https://llm.ctz.fyi")
|
||||
VIKUNJA_BASE_URL = os.environ.get("VIKUNJA_BASE_URL", "https://tasks.ctz.fyi")
|
||||
FORGEJO_BASE_URL = os.environ.get("FORGEJO_BASE_URL", "https://git.ctz.fyi")
|
||||
|
||||
HOME = Path(os.environ.get("HOME", "/home/agent"))
|
||||
CONFIG_DIR = HOME / ".config" / "opencode"
|
||||
|
||||
|
||||
def get_openbao_token() -> str:
|
||||
resp = httpx.post(
|
||||
f"{OPENBAO_ADDR}/v1/auth/approle/login",
|
||||
json={"role_id": OPENBAO_ROLE_ID, "secret_id": OPENBAO_SECRET_ID},
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["auth"]["client_token"]
|
||||
|
||||
|
||||
def get_secret(bao_token: str, path: str, key: str) -> str:
|
||||
resp = httpx.get(
|
||||
f"{OPENBAO_ADDR}/v1/secret/data/{path}",
|
||||
headers={"X-Vault-Token": bao_token},
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["data"]["data"][key]
|
||||
|
||||
|
||||
def fetch_role_secrets(bao_token: str, role: str) -> dict:
|
||||
"""Fetch all secrets for a role. Returns dict of secret_name -> value."""
|
||||
secrets = {}
|
||||
secret_names = ["litellm-key", "vikunja-token", "forgejo-token", "argocd-token"]
|
||||
for name in secret_names:
|
||||
try:
|
||||
key = "token" if name != "litellm-key" else "key"
|
||||
secrets[name] = get_secret(bao_token, f"autojanet/{role}/{name}", key)
|
||||
log.info("Fetched secret: %s", name)
|
||||
except Exception as e:
|
||||
log.warning("Could not fetch %s: %s", name, e)
|
||||
return secrets
|
||||
|
||||
|
||||
def write_opencode_config(secrets: dict, role: str) -> None:
|
||||
"""Write opencode config with the agent's secrets and MCP server tokens."""
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
litellm_key = secrets.get("litellm-key", "")
|
||||
vikunja_token = secrets.get("vikunja-token", "")
|
||||
forgejo_token = secrets.get("forgejo-token", "")
|
||||
|
||||
config = {
|
||||
"model": f"litellm/copilot/claude-sonnet-4.6",
|
||||
"providers": {
|
||||
"litellm": {
|
||||
"apiKey": litellm_key,
|
||||
"baseURL": f"{LITELLM_BASE_URL}/v1",
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"vikunja": {
|
||||
"type": "sse",
|
||||
"url": f"{LITELLM_BASE_URL}/mcp/vikunja",
|
||||
"headers": {
|
||||
"x-vikunja-token": vikunja_token,
|
||||
}
|
||||
},
|
||||
"forgejo": {
|
||||
"type": "sse",
|
||||
"url": f"{LITELLM_BASE_URL}/mcp/forgejo",
|
||||
"headers": {
|
||||
"x-forgejo-token": forgejo_token,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
config_path = CONFIG_DIR / "config.json"
|
||||
config_path.write_text(json.dumps(config, indent=2))
|
||||
log.info("Wrote opencode config to %s", config_path)
|
||||
|
||||
# Write AGENTS.md with role-specific instructions
|
||||
agent_md_src = Path(f"/app/agents/{role}.agent.md")
|
||||
agents_md_dst = CONFIG_DIR / "AGENTS.md"
|
||||
if agent_md_src.exists():
|
||||
agents_md_dst.write_text(agent_md_src.read_text())
|
||||
log.info("Loaded agent instructions from %s", agent_md_src)
|
||||
else:
|
||||
log.warning("No agent file found at %s", agent_md_src)
|
||||
|
||||
|
||||
def build_prompt(task_id: str, task_title: str) -> str:
|
||||
return f"""You are the AutoJanet agent for role: {AGENT_ROLE}
|
||||
|
||||
Your current task (Vikunja task #{task_id}):
|
||||
{task_title}
|
||||
|
||||
Instructions:
|
||||
1. Read the task carefully.
|
||||
2. Fetch full task details from Vikunja if needed.
|
||||
3. Complete the task using the tools available to you.
|
||||
4. Move the task to Done in Vikunja when complete.
|
||||
5. Open a PR if code was written.
|
||||
6. Do not ask for confirmation — act autonomously within your constraints.
|
||||
"""
|
||||
|
||||
|
||||
def run_opencode(prompt: str) -> int:
|
||||
"""Run opencode non-interactively with the given prompt."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
f.write(prompt)
|
||||
prompt_file = f.name
|
||||
|
||||
cmd = ["opencode", "run", "--no-input", "--prompt-file", prompt_file]
|
||||
log.info("Running: %s", " ".join(cmd))
|
||||
|
||||
result = subprocess.run(cmd, check=False)
|
||||
return result.returncode
|
||||
|
||||
|
||||
def main() -> None:
|
||||
log.info("Agent entrypoint: role=%s task=%s", AGENT_ROLE, TASK_ID)
|
||||
|
||||
bao_token = get_openbao_token()
|
||||
log.info("OpenBao authenticated")
|
||||
|
||||
secrets = fetch_role_secrets(bao_token, AGENT_ROLE)
|
||||
write_opencode_config(secrets, AGENT_ROLE)
|
||||
|
||||
prompt = build_prompt(TASK_ID, TASK_TITLE)
|
||||
rc = run_opencode(prompt)
|
||||
|
||||
if rc != 0:
|
||||
log.error("opencode exited with code %d", rc)
|
||||
sys.exit(rc)
|
||||
|
||||
log.info("Agent completed task %s successfully", TASK_ID)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
container/requirements.txt
Normal file
2
container/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
httpx>=0.27
|
||||
kubernetes>=29.0
|
||||
308
dispatcher/dispatcher.py
Normal file
308
dispatcher/dispatcher.py
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
AutoJanet Dispatcher
|
||||
|
||||
Runs as a CronJob every 2 minutes. Scans Vikunja project 78 for tasks in the
|
||||
Todo bucket that have an `agent:<role>` label. Claims each task (moves to
|
||||
In Progress) and spawns a Kubernetes Job for the appropriate agent.
|
||||
|
||||
Requirements:
|
||||
- OPENBAO_ADDR, OPENBAO_ROLE_ID, OPENBAO_SECRET_ID — for fetching Vikunja token
|
||||
- VIKUNJA_BASE_URL, VIKUNJA_PROJECT_ID, VIKUNJA_TODO_BUCKET_ID
|
||||
- K8S_NAMESPACE, AGENT_IMAGE
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from string import Template
|
||||
|
||||
import httpx
|
||||
from kubernetes import client as k8s_client, config as k8s_config
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
log = logging.getLogger("dispatcher")
|
||||
|
||||
# ── Config ────────────────────────────────────────────────────────────────────
|
||||
|
||||
OPENBAO_ADDR = os.environ["OPENBAO_ADDR"]
|
||||
OPENBAO_ROLE_ID = os.environ["OPENBAO_ROLE_ID"]
|
||||
OPENBAO_SECRET_ID = os.environ["OPENBAO_SECRET_ID"]
|
||||
|
||||
VIKUNJA_BASE_URL = os.environ.get("VIKUNJA_BASE_URL", "https://tasks.ctz.fyi")
|
||||
VIKUNJA_PROJECT_ID = int(os.environ.get("VIKUNJA_PROJECT_ID", "78"))
|
||||
VIKUNJA_TODO_BUCKET_ID = int(os.environ.get("VIKUNJA_TODO_BUCKET_ID", "116"))
|
||||
VIKUNJA_IN_PROGRESS_BUCKET_ID = int(os.environ.get("VIKUNJA_IN_PROGRESS_BUCKET_ID", "117"))
|
||||
|
||||
K8S_NAMESPACE = os.environ.get("K8S_NAMESPACE", "autojanet")
|
||||
AGENT_IMAGE = os.environ.get("AGENT_IMAGE", "registry.ctz.fyi/autojanet/agent:latest")
|
||||
|
||||
VALID_ROLES = {
|
||||
"pm", "coder", "code-reviewer", "test-engineer", "devsecops", "secops",
|
||||
"sre", "kubernetes-pilot", "linux-admin", "systems-engineer", "networking",
|
||||
"dba", "prometheus-expert", "tofu-engineer", "release-manager",
|
||||
"doc-updater", "doc-writer", "technical-writer", "cost-optimizer",
|
||||
}
|
||||
|
||||
# ── OpenBao ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_openbao_token() -> str:
|
||||
"""Authenticate to OpenBao via AppRole and return a client token."""
|
||||
resp = httpx.post(
|
||||
f"{OPENBAO_ADDR}/v1/auth/approle/login",
|
||||
json={"role_id": OPENBAO_ROLE_ID, "secret_id": OPENBAO_SECRET_ID},
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["auth"]["client_token"]
|
||||
|
||||
|
||||
def get_secret(bao_token: str, path: str, key: str) -> str:
|
||||
"""Read a KV v2 secret from OpenBao."""
|
||||
resp = httpx.get(
|
||||
f"{OPENBAO_ADDR}/v1/secret/data/{path}",
|
||||
headers={"X-Vault-Token": bao_token},
|
||||
timeout=10,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["data"]["data"][key]
|
||||
|
||||
|
||||
# ── Vikunja ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_vikunja_token(bao_token: str) -> str:
|
||||
"""Fetch the dispatcher's Vikunja token from OpenBao."""
|
||||
return get_secret(bao_token, "autojanet/pm/vikunja-token", "token")
|
||||
|
||||
|
||||
def list_todo_tasks(vikunja_token: str) -> list[dict]:
|
||||
"""Return all tasks in the Todo bucket of the AutoJanet project."""
|
||||
tasks = []
|
||||
page = 1
|
||||
while True:
|
||||
resp = httpx.get(
|
||||
f"{VIKUNJA_BASE_URL}/api/v1/projects/{VIKUNJA_PROJECT_ID}/tasks",
|
||||
headers={"Authorization": f"Bearer {vikunja_token}"},
|
||||
params={"page": page, "per_page": 50},
|
||||
timeout=15,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
batch = resp.json()
|
||||
if not batch:
|
||||
break
|
||||
tasks.extend(batch)
|
||||
if len(batch) < 50:
|
||||
break
|
||||
page += 1
|
||||
# Filter to Todo bucket only
|
||||
return [t for t in tasks if t.get("bucket_id") == VIKUNJA_TODO_BUCKET_ID]
|
||||
|
||||
|
||||
def extract_agent_role(task: dict) -> str | None:
|
||||
"""
|
||||
Return the role name if the task has exactly one `agent:<role>` label
|
||||
that matches a known role. Returns None otherwise.
|
||||
"""
|
||||
labels = task.get("labels") or []
|
||||
roles_found = []
|
||||
for label in labels:
|
||||
title = label.get("title", "")
|
||||
m = re.match(r"^agent:(.+)$", title)
|
||||
if m:
|
||||
role = m.group(1)
|
||||
if role in VALID_ROLES:
|
||||
roles_found.append(role)
|
||||
if len(roles_found) == 1:
|
||||
return roles_found[0]
|
||||
return None
|
||||
|
||||
|
||||
def claim_task(vikunja_token: str, task_id: int) -> bool:
|
||||
"""Move task to In Progress bucket. Returns True on success."""
|
||||
resp = httpx.post(
|
||||
f"{VIKUNJA_BASE_URL}/api/v1/tasks/{task_id}",
|
||||
headers={
|
||||
"Authorization": f"Bearer {vikunja_token}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={"bucket_id": VIKUNJA_IN_PROGRESS_BUCKET_ID},
|
||||
timeout=10,
|
||||
)
|
||||
if resp.status_code in (200, 201):
|
||||
return True
|
||||
log.warning("Failed to claim task %d: %d %s", task_id, resp.status_code, resp.text)
|
||||
return False
|
||||
|
||||
|
||||
# ── Kubernetes ────────────────────────────────────────────────────────────────
|
||||
|
||||
def load_k8s_config() -> None:
|
||||
try:
|
||||
k8s_config.load_incluster_config()
|
||||
except k8s_config.ConfigException:
|
||||
k8s_config.load_kube_config()
|
||||
|
||||
|
||||
def job_name(role: str, task_id: int) -> str:
|
||||
safe_role = role.replace("-", "")[:12]
|
||||
return f"agent-{safe_role}-{task_id}"
|
||||
|
||||
|
||||
def job_already_exists(batch_v1: k8s_client.BatchV1Api, name: str) -> bool:
|
||||
try:
|
||||
batch_v1.read_namespaced_job(name=name, namespace=K8S_NAMESPACE)
|
||||
return True
|
||||
except k8s_client.ApiException as e:
|
||||
if e.status == 404:
|
||||
return False
|
||||
raise
|
||||
|
||||
|
||||
def spawn_agent_job(
|
||||
batch_v1: k8s_client.BatchV1Api,
|
||||
role: str,
|
||||
task_id: int,
|
||||
task_title: str,
|
||||
) -> None:
|
||||
name = job_name(role, task_id)
|
||||
if job_already_exists(batch_v1, name):
|
||||
log.info("Job %s already exists, skipping", name)
|
||||
return
|
||||
|
||||
job = k8s_client.V1Job(
|
||||
api_version="batch/v1",
|
||||
kind="Job",
|
||||
metadata=k8s_client.V1ObjectMeta(
|
||||
name=name,
|
||||
namespace=K8S_NAMESPACE,
|
||||
labels={
|
||||
"autojanet/type": "agent",
|
||||
"autojanet/role": role,
|
||||
"autojanet/task-id": str(task_id),
|
||||
},
|
||||
),
|
||||
spec=k8s_client.V1JobSpec(
|
||||
ttl_seconds_after_finished=3600,
|
||||
backoff_limit=1,
|
||||
template=k8s_client.V1PodTemplateSpec(
|
||||
metadata=k8s_client.V1ObjectMeta(
|
||||
labels={
|
||||
"autojanet/type": "agent",
|
||||
"autojanet/role": role,
|
||||
"autojanet/task-id": str(task_id),
|
||||
}
|
||||
),
|
||||
spec=k8s_client.V1PodSpec(
|
||||
service_account_name=f"agent-{role}",
|
||||
restart_policy="Never",
|
||||
node_selector={"kubernetes.io/arch": "amd64"},
|
||||
containers=[
|
||||
k8s_client.V1Container(
|
||||
name="agent",
|
||||
image=AGENT_IMAGE,
|
||||
image_pull_policy="Always",
|
||||
env=[
|
||||
k8s_client.V1EnvVar(name="AGENT_ROLE", value=role),
|
||||
k8s_client.V1EnvVar(name="TASK_ID", value=str(task_id)),
|
||||
k8s_client.V1EnvVar(name="TASK_TITLE", value=task_title),
|
||||
k8s_client.V1EnvVar(name="OPENBAO_ADDR", value=OPENBAO_ADDR),
|
||||
k8s_client.V1EnvVar(name="LITELLM_BASE_URL", value="https://llm.ctz.fyi"),
|
||||
k8s_client.V1EnvVar(name="VIKUNJA_BASE_URL", value=VIKUNJA_BASE_URL),
|
||||
k8s_client.V1EnvVar(name="FORGEJO_BASE_URL", value="https://git.ctz.fyi"),
|
||||
k8s_client.V1EnvVar(
|
||||
name="OPENBAO_ROLE_ID",
|
||||
value_from=k8s_client.V1EnvVarSource(
|
||||
secret_key_ref=k8s_client.V1SecretKeySelector(
|
||||
name=f"agent-{role}-approle",
|
||||
key="role_id",
|
||||
)
|
||||
),
|
||||
),
|
||||
k8s_client.V1EnvVar(
|
||||
name="OPENBAO_SECRET_ID",
|
||||
value_from=k8s_client.V1EnvVarSource(
|
||||
secret_key_ref=k8s_client.V1SecretKeySelector(
|
||||
name=f"agent-{role}-approle",
|
||||
key="secret_id",
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
resources=k8s_client.V1ResourceRequirements(
|
||||
requests={"cpu": "250m", "memory": "512Mi"},
|
||||
limits={"cpu": "2000m", "memory": "2Gi"},
|
||||
),
|
||||
security_context=k8s_client.V1SecurityContext(
|
||||
allow_privilege_escalation=False,
|
||||
run_as_non_root=True,
|
||||
run_as_user=1000,
|
||||
capabilities=k8s_client.V1Capabilities(drop=["ALL"]),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
batch_v1.create_namespaced_job(namespace=K8S_NAMESPACE, body=job)
|
||||
log.info("Spawned job %s for role=%s task=%d", name, role, task_id)
|
||||
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main() -> None:
|
||||
log.info("Dispatcher starting")
|
||||
|
||||
# Auth
|
||||
bao_token = get_openbao_token()
|
||||
vikunja_token = get_vikunja_token(bao_token)
|
||||
log.info("Authenticated to OpenBao and Vikunja")
|
||||
|
||||
# k8s
|
||||
load_k8s_config()
|
||||
batch_v1 = k8s_client.BatchV1Api()
|
||||
|
||||
# Scan tasks
|
||||
tasks = list_todo_tasks(vikunja_token)
|
||||
log.info("Found %d tasks in Todo bucket", len(tasks))
|
||||
|
||||
claimed = 0
|
||||
for task in tasks:
|
||||
task_id = task["id"]
|
||||
title = task.get("title", "")
|
||||
role = extract_agent_role(task)
|
||||
|
||||
if not role:
|
||||
log.debug("Task %d has no valid agent label, skipping", task_id)
|
||||
continue
|
||||
|
||||
log.info("Claiming task %d (%s) for role=%s", task_id, title[:60], role)
|
||||
if not claim_task(vikunja_token, task_id):
|
||||
continue
|
||||
|
||||
try:
|
||||
spawn_agent_job(batch_v1, role, task_id, title)
|
||||
claimed += 1
|
||||
except Exception as e:
|
||||
log.error("Failed to spawn job for task %d: %s", task_id, e)
|
||||
# Un-claim: move back to Todo
|
||||
httpx.post(
|
||||
f"{VIKUNJA_BASE_URL}/api/v1/tasks/{task_id}",
|
||||
headers={"Authorization": f"Bearer {vikunja_token}", "Content-Type": "application/json"},
|
||||
json={"bucket_id": VIKUNJA_TODO_BUCKET_ID},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
log.info("Dispatcher done. Claimed %d tasks.", claimed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
dispatcher/requirements.txt
Normal file
2
dispatcher/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
httpx>=0.27
|
||||
kubernetes>=29.0
|
||||
26
k8s/manifests/clustersecretstore.yaml
Normal file
26
k8s/manifests/clustersecretstore.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
# ExternalSecret: pull agent AppRole credentials from OpenBao into k8s Secrets
|
||||
# One ExternalSecret per role. Template shown for pm; others follow same pattern.
|
||||
# Deploy via: kubectl apply -f externalsecrets/ (generated per-role)
|
||||
#
|
||||
# Prerequisites:
|
||||
# - external-secrets operator installed
|
||||
# - ClusterSecretStore "openbao" configured pointing to OpenBao in-cluster
|
||||
#
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: openbao
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: "http://openbao.openbao.svc.cluster.local:8200"
|
||||
path: "secret"
|
||||
version: "v2"
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: "kubernetes"
|
||||
role: "external-secrets"
|
||||
serviceAccountRef:
|
||||
name: "external-secrets"
|
||||
namespace: "external-secrets"
|
||||
68
k8s/manifests/dispatcher-cronjob.yaml
Normal file
68
k8s/manifests/dispatcher-cronjob.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
# Dispatcher CronJob — runs every 2 minutes, claims unclaimed tasks from Vikunja
|
||||
# and spawns agent Jobs for each
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: dispatcher
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: dispatcher
|
||||
spec:
|
||||
schedule: "*/2 * * * *"
|
||||
concurrencyPolicy: Forbid # never run two dispatchers simultaneously
|
||||
successfulJobsHistoryLimit: 5
|
||||
failedJobsHistoryLimit: 5
|
||||
jobTemplate:
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
autojanet/role: dispatcher
|
||||
spec:
|
||||
serviceAccountName: dispatcher
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: dispatcher
|
||||
image: registry.ctz.fyi/autojanet/dispatcher:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: OPENBAO_ADDR
|
||||
value: "http://openbao.openbao.svc.cluster.local:8200"
|
||||
- name: OPENBAO_ROLE_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dispatcher-approle
|
||||
key: role_id
|
||||
- name: OPENBAO_SECRET_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dispatcher-approle
|
||||
key: secret_id
|
||||
- name: VIKUNJA_BASE_URL
|
||||
value: "https://tasks.ctz.fyi"
|
||||
- name: VIKUNJA_PROJECT_ID
|
||||
value: "78"
|
||||
- name: VIKUNJA_TODO_BUCKET_ID
|
||||
value: "116"
|
||||
- name: VIKUNJA_IN_PROGRESS_BUCKET_ID
|
||||
value: "117"
|
||||
- name: K8S_NAMESPACE
|
||||
value: "autojanet"
|
||||
- name: AGENT_IMAGE
|
||||
value: "registry.ctz.fyi/autojanet/agent:latest"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
75
k8s/manifests/job-template.yaml
Normal file
75
k8s/manifests/job-template.yaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
# Agent Job Template
|
||||
# The dispatcher renders this template per-task, substituting:
|
||||
# AGENT_ROLE, TASK_ID, TASK_TITLE
|
||||
#
|
||||
# This is a reference template — not applied directly.
|
||||
# The dispatcher generates Job manifests from this pattern.
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: agent-${ROLE}-${TASK_ID}
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/type: agent
|
||||
autojanet/role: ${ROLE}
|
||||
autojanet/task-id: "${TASK_ID}"
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 3600 # clean up after 1 hour
|
||||
backoffLimit: 1 # retry once on failure
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
autojanet/type: agent
|
||||
autojanet/role: ${ROLE}
|
||||
autojanet/task-id: "${TASK_ID}"
|
||||
spec:
|
||||
serviceAccountName: agent-${ROLE}
|
||||
restartPolicy: Never
|
||||
# CPU nodes only — GPU reserved for LiteLLM
|
||||
nodeSelector:
|
||||
kubernetes.io/arch: amd64
|
||||
tolerations: []
|
||||
containers:
|
||||
- name: agent
|
||||
image: registry.ctz.fyi/autojanet/agent:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: AGENT_ROLE
|
||||
value: "${ROLE}"
|
||||
- name: TASK_ID
|
||||
value: "${TASK_ID}"
|
||||
- name: TASK_TITLE
|
||||
value: "${TASK_TITLE}"
|
||||
- name: OPENBAO_ROLE_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-${ROLE}-approle
|
||||
key: role_id
|
||||
- name: OPENBAO_SECRET_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-${ROLE}-approle
|
||||
key: secret_id
|
||||
- name: OPENBAO_ADDR
|
||||
value: "http://openbao.openbao.svc.cluster.local:8200"
|
||||
- name: LITELLM_BASE_URL
|
||||
value: "https://llm.ctz.fyi"
|
||||
- name: VIKUNJA_BASE_URL
|
||||
value: "https://tasks.ctz.fyi"
|
||||
- name: FORGEJO_BASE_URL
|
||||
value: "https://git.ctz.fyi"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "250m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2000m"
|
||||
memory: "2Gi"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
7
k8s/manifests/namespace.yaml
Normal file
7
k8s/manifests/namespace.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: autojanet
|
||||
labels:
|
||||
app.kubernetes.io/name: autojanet
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
80
k8s/policies/networkpolicy.yaml
Normal file
80
k8s/policies/networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
# Default-deny all ingress and egress in autojanet namespace
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny-all
|
||||
namespace: autojanet
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
---
|
||||
# Allow agents to reach the internet (APIs: Vikunja, Forgejo, LiteLLM, OpenBao, Grafana, etc.)
|
||||
# All external services are HTTPS on 443; OpenBao internal is 8200
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-egress-external
|
||||
namespace: autojanet
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
autojanet/type: agent
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# HTTPS to external services
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
# Internal cluster DNS
|
||||
- ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
# OpenBao in-cluster (openbao namespace)
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: openbao
|
||||
ports:
|
||||
- port: 8200
|
||||
protocol: TCP
|
||||
# k8s API server (for kubectl-capable agents)
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- port: 6443
|
||||
protocol: TCP
|
||||
---
|
||||
# Allow dispatcher egress to k8s API and OpenBao only
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-dispatcher-egress
|
||||
namespace: autojanet
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
autojanet/role: dispatcher
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: openbao
|
||||
ports:
|
||||
- port: 8200
|
||||
protocol: TCP
|
||||
205
k8s/rbac/serviceaccounts.yaml
Normal file
205
k8s/rbac/serviceaccounts.yaml
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
---
|
||||
# ServiceAccount per agent role
|
||||
# One SA per role — bound to its own OpenBao AppRole secret
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-pm
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: pm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-coder
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: coder
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-code-reviewer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: code-reviewer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-test-engineer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: test-engineer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-devsecops
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: devsecops
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-secops
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: secops
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-sre
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: sre
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-kubernetes-pilot
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: kubernetes-pilot
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-linux-admin
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: linux-admin
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-systems-engineer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: systems-engineer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-networking
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: networking
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-dba
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: dba
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-prometheus-expert
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: prometheus-expert
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-tofu-engineer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: tofu-engineer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-release-manager
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: release-manager
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-doc-updater
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: doc-updater
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-doc-writer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: doc-writer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-technical-writer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: technical-writer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: agent-cost-optimizer
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: cost-optimizer
|
||||
---
|
||||
# Dispatcher ServiceAccount — runs the CronJob that claims tasks
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dispatcher
|
||||
namespace: autojanet
|
||||
labels:
|
||||
autojanet/role: dispatcher
|
||||
---
|
||||
# Role: agents can create/manage Jobs in their own namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: agent-job-runner
|
||||
namespace: autojanet
|
||||
rules:
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["create", "get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/log"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
# Dispatcher gets broader job management
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: dispatcher
|
||||
namespace: autojanet
|
||||
rules:
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["create", "get", "list", "watch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/log", "configmaps"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: dispatcher
|
||||
namespace: autojanet
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: dispatcher
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: dispatcher
|
||||
namespace: autojanet
|
||||
Loading…
Reference in a new issue