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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .osv-scanner.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ reason = "go.etcd.io/bbolt v1.4.3 dep, no fix available"
[[IgnoredVulns]]
id = "GHSA-xmrv-pmrh-hhx2"
reason = "aws-sdk-go-v2 s3/eventstream, upgrade pending"

[[IgnoredVulns]]
id = "GO-2026-5932"
reason = "golang.org/x/crypto/openpgp unmaintained, no fix will ship — advisory deprecates the package"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/).
* MINOR version when you add functionality in a backwards-compatible manner, and
* PATCH version when you make backwards-compatible bug fixes.

## Unreleased
- suppress no-fix advisory GO-2026-5932 (golang.org/x/crypto/openpgp unmaintained) in govulncheck + osv-scanner so precommit is green
- launch-agent: flexible repo naming — suggest-with-override (`<slug>-agent` / `github-<slug>-agent` for GitHub-triggered, freely overridable) instead of forcing `bborbe/agent-<name>`; aligns with the post-split fleet convention (github-pr-review-agent, github-releaser-agent)

## v0.77.0

- helm: when `executor.kafkaUser.enabled`, pass the executor's own client-cert + CA secret names to the executor as `JOB_KAFKA_CLIENT_CERT_SECRET` / `JOB_KAFKA_CA_CERT_SECRET` env, so it mounts mTLS Kafka certs into the per-task agent Jobs it spawns (agent-task-executor ≥ v0.4.0). Fixes spawned agent Jobs (pr-reviewer, github-releaser) crashing on `open /client-cert/file: no such file` against mTLS Kafka. Default off (`kafkaUser` disabled) → no env, spawned Jobs unchanged. Chart 0.4.1 → 0.5.0.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ vet:
.PHONY: vulncheck
vulncheck:
@go run golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) -format json $(shell go list -mod=mod ./... | grep -v /vendor/) 2>&1 | \
jq -e 'select(.finding != null and .finding.osv != "GO-2026-4923" and .finding.osv != "GO-2026-4514" and .finding.osv != "GO-2022-0470" and .finding.osv != "GO-2026-4772" and .finding.osv != "GO-2026-4771")' > /dev/null 2>&1 && \
jq -e 'select(.finding != null and .finding.osv != "GO-2026-4923" and .finding.osv != "GO-2026-4514" and .finding.osv != "GO-2022-0470" and .finding.osv != "GO-2026-4772" and .finding.osv != "GO-2026-4771" and .finding.osv != "GO-2026-5932")' > /dev/null 2>&1 && \
{ echo "Unexpected vulnerabilities found"; go run golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) $(shell go list -mod=mod ./... | grep -v /vendor/); exit 1; } || \
echo "No unignored vulnerabilities found"

Expand Down
8 changes: 4 additions & 4 deletions commands/launch-agent.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Interview-driven scaffolding for a new bborbe agent — clones a template repo, generates Config CRD + vault page + first scenario
argument-hint: "[agent name] [--shape claude|code|gemini|pi]"
argument-hint: "[role or repo-name] [--shape claude|code|gemini|pi]"
allowed-tools: [Task, Read, Write, Edit, Bash, AskUserQuestion, mcp__semantic-search__search_related]
---

Expand All @@ -10,7 +10,7 @@ Scaffold a new bborbe agent end-to-end via interview + template clone. Delegate

<process>
1. **Parse arguments** from `$ARGUMENTS`:
- First positional token (if any): proposed agent name (will be normalized by the skill)
- First positional token (if any): proposed role or repo-name seed. The skill suggests `<slug>-agent` (or `github-<slug>-agent` for GitHub-triggered) and lets the user overwrite with any valid repo name — no forced `agent-` prefix
- `--shape <shape>`: explicit shape pick (skip the shape-picker subagent)
- No args: skill runs the full interview from scratch

Expand All @@ -22,7 +22,7 @@ Scaffold a new bborbe agent end-to-end via interview + template clone. Delegate
- Read references (shapes.md, interview.md, templates)
- Run [[Agent Design Guide]] 45-Q interview (conversational, AskUserQuestion for enumerable choices)
- Recommend shape via `agent-shape-picker` subagent if `--shape` not provided
- `gh repo create bborbe/agent-<name> --public --template bborbe/agent-<shape>`
- `gh repo create bborbe/<name> --public --template bborbe/agent-<shape>`
- Clone, rename Go module path + package names + Config kind across files
- Generate `Config.yaml` (CRD instance), `agent/<name>/.claude/CLAUDE.md`, `README.md` adapted
- Write vault artifacts: `50 Knowledge Base/<Name> Agent.md`, `23 Goals/Build <Name> Agent.md`, `24 Tasks/Bootstrap <Name> Agent.md`, scenario stub
Expand All @@ -32,7 +32,7 @@ Scaffold a new bborbe agent end-to-end via interview + template clone. Delegate
<success_criteria>
- Skill invoked successfully
- New repo created on GitHub via `--template` flag
- Local clone exists at `~/Documents/workspaces/agent-<name>/`
- Local clone exists at `~/Documents/workspaces/<name>/`
- Vault artifacts created in the configured vault (Personal)
- Deploy checklist printed
</success_criteria>
Expand Down
82 changes: 43 additions & 39 deletions skills/launch-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,21 @@ Walk through `references/interview.md` conversationally. Use `AskUserQuestion` f
- Part 7 (Safety): consent gates, error handling per class, security boundaries
- Part 8 (Acceptance): per-phase acceptance criteria, overall DoD

After Part 2 (name picked), normalize the agent name:

1. Lowercase, strip leading/trailing whitespace
2. Replace runs of `[^a-z0-9-]` with single `-`
3. Strip leading/trailing `-`
4. Drop any leading `agent-` prefix (the new repo will be `bborbe/agent-<name>`)
5. **Reject** if final name contains any of: `$`, backtick, `;`, `|`, `<`, `>`, `&`, `(`, `)`, `\`, `..`, `/` — these can't appear in valid GitHub repo names and would be dangerous in shell interpolation later
6. **Reject** if name is empty after normalization, starts with `.`, or matches `agent` exactly (reserved for the SDK repo)
7. **Reject** if length > 50 chars (GitHub repo name limit + safety margin)

On rejection, surface the issue to the user via `AskUserQuestion` and ask for a different name.
After Part 2 (role captured), derive the **repo name** via suggest-with-override. The repo name IS the GitHub repo basename (`bborbe/<name>`) — do NOT force an `agent-` prefix; the fleet convention is a `-agent` SUFFIX (`github-pr-review-agent`, `github-releaser-agent`).

1. **Normalize the role** the user gave into a kebab slug: lowercase, strip leading/trailing whitespace, replace runs of `[^a-z0-9-]` with a single `-`, strip leading/trailing `-`. Call this `<slug>` (e.g. `dark factory` → `dark-factory`).
2. **Compute the suggested repo name** `<suggested>` — robustly, so re-running on an already-suffixed name is idempotent:
- `<core>` = `<slug>` with a single trailing `-agent` removed if present (so `pr-review` and `pr-review-agent` both give core `pr-review` — no double suffix).
- `<suggested>` = `<core>-agent`.
- Do NOT auto-prepend `github-`; if the user wants the `github-<x>-agent` form (valid — see note below), they add it via the override in step 3. This keeps the suggestion a single, predictable shape.
3. **Offer it via `AskUserQuestion`**, `<suggested>` as the recommended option, plus an "Other" free-text so the user can **overwrite with any repo name they want** (e.g. `github-dark-factory-agent`, or a bare `foo`). The user's choice — suggested or override — becomes `<name>`, used verbatim as the repo basename everywhere below.
4. **Validate `<name>`** (the final chosen value):
- **Reject** if it contains any of: `$`, backtick, `;`, `|`, `<`, `>`, `&`, `(`, `)`, `\`, `..`, `/` — invalid in GitHub repo names and unsafe in later shell interpolation.
- **Reject** if empty, starts with `.`, or equals `agent` exactly (reserved for the SDK repo).
- **Reject** if length > 50 chars (GitHub repo name limit + safety margin).
- **Do NOT reject a `github-` prefix.** GitHub reserves `github` only for org/user *account* names, not repo names — `bborbe/github-pr-review-agent` and `bborbe/github-releaser-agent` already exist. `github-<x>-agent` is a valid, in-use repo name and the operator may choose it via override.

On rejection, surface the issue via `AskUserQuestion` and re-offer (suggested name + Other).

**Gate 1**: confirm captured intent with the user before proceeding to shape pick:
> "Captured: <one-paragraph summary of name + purpose + trigger + key constraints>. Proceed to shape recommendation?"
Expand Down Expand Up @@ -77,16 +81,16 @@ Present to user via `AskUserQuestion`:
Use `gh repo create` with the `--template` flag:

```bash
gh repo create bborbe/agent-<name> --public \
gh repo create bborbe/<name> --public \
--template bborbe/agent-<shape> \
--description "<sanitized one-line purpose>"
```

Then clone:

```bash
git clone git@github.com:bborbe/agent-<name>.git ~/Documents/workspaces/agent-<name>
cd ~/Documents/workspaces/agent-<name>
git clone git@github.com:bborbe/<name>.git ~/Documents/workspaces/<name>
cd ~/Documents/workspaces/<name>
```

### Phase 3 — gh API error handling
Expand All @@ -99,7 +103,7 @@ cd ~/Documents/workspaces/agent-<name>
| `HTTP 403: rate limit exceeded` | gh API quota burned | HALT — surface the reset window from `gh api rate_limit`. Recovery: wait for window reset, re-invoke `/launch-agent`. |
| `HTTP 401: Bad credentials` | `gh auth status` is bad | HALT — surface "run `gh auth login` then re-invoke". |
| `HTTP 404` on `--template` source | template repo doesn't exist or isn't flagged `is_template: true` | HALT — surface "verify `gh api repos/bborbe/agent-<shape> --jq .is_template` returns true; if not, run `gh api repos/bborbe/agent-<shape> --method PATCH --field is_template=true`". |
| `git clone` fails after repo created | SSH key issue / network blip | HALT — `gh repo delete bborbe/agent-<name> --yes` to clean up the empty remote, then user investigates SSH + re-invokes. |
| `git clone` fails after repo created | SSH key issue / network blip | HALT — `gh repo delete bborbe/<name> --yes` to clean up the empty remote, then user investigates SSH + re-invokes. |

Always print the raw `gh` stderr so the user has the actual diagnostic. The table above documents the common cases; novel failures get reported verbatim.

Expand All @@ -109,16 +113,16 @@ Mechanical renames across the cloned template. **Portable sed**: use `sed -i.bak

For files that may not exist in every template (some shapes don't have a `Makefile.precommit`, some don't have a `k8s/` dir), wrap the sed in a `[ -f "<file>" ] &&` existence guard so a missing file is a no-op rather than a silent failure.

1. **`go.mod`**: change `module github.com/bborbe/agent-<shape>` → `module github.com/bborbe/agent-<name>`
2. **`.go` files**: `find . -name '*.go' -exec sed -i.bak 's|github.com/bborbe/agent-<shape>|github.com/bborbe/agent-<name>|g' {} +` then `find . -name '*.go.bak' -delete`
3. **`Makefile`**: `[ -f Makefile ] && sed -i.bak 's|SERVICE = agent-<shape>|SERVICE = agent-<name>|' Makefile && rm -f Makefile.bak`
4. **`Makefile.precommit`**: `[ -f Makefile.precommit ] && sed -i.bak 's|github.com/bborbe/agent-<shape>|github.com/bborbe/agent-<name>|' Makefile.precommit && rm -f Makefile.precommit.bak`
5. **`example.env`**: `[ -f example.env ] && sed -i.bak 's|bborbe/agent-<shape>|bborbe/agent-<name>|' example.env && rm -f example.env.bak`
6. **k8s/ YAMLs** (skip if `k8s/` doesn't exist): rename files + resources to `agent-<name>`:
- `git mv k8s/agent-<shape>.yaml k8s/agent-<name>.yaml`
- `git mv k8s/agent-<shape>-secret.yaml k8s/agent-<name>-secret.yaml`
- `git mv k8s/agent-<shape>-pvc.yaml k8s/agent-<name>-pvc.yaml` (if shape has one)
- `sed -i.bak 's|agent-<shape>|agent-<name>|g' k8s/*.yaml && rm -f k8s/*.bak`
1. **`go.mod`**: change `module github.com/bborbe/agent-<shape>` → `module github.com/bborbe/<name>`
2. **`.go` files**: `find . -name '*.go' -exec sed -i.bak 's|github.com/bborbe/agent-<shape>|github.com/bborbe/<name>|g' {} +` then `find . -name '*.go.bak' -delete`
3. **`Makefile`**: `[ -f Makefile ] && sed -i.bak 's|SERVICE = agent-<shape>|SERVICE = <name>|' Makefile && rm -f Makefile.bak`
4. **`Makefile.precommit`**: `[ -f Makefile.precommit ] && sed -i.bak 's|github.com/bborbe/agent-<shape>|github.com/bborbe/<name>|' Makefile.precommit && rm -f Makefile.precommit.bak`
5. **`example.env`**: `[ -f example.env ] && sed -i.bak 's|bborbe/agent-<shape>|bborbe/<name>|' example.env && rm -f example.env.bak`
6. **k8s/ YAMLs** (skip if `k8s/` doesn't exist): rename files + resources to `<name>`:
- `git mv k8s/agent-<shape>.yaml k8s/<name>.yaml`
- `git mv k8s/agent-<shape>-secret.yaml k8s/<name>-secret.yaml`
- `git mv k8s/agent-<shape>-pvc.yaml k8s/<name>-pvc.yaml` (if shape has one)
- `sed -i.bak 's|agent-<shape>|<name>|g' k8s/*.yaml && rm -f k8s/*.bak`
7. **README.md**: rewrite the top section to reflect the new agent's purpose (use captured Part 1 + Part 2 from interview)
8. **CHANGELOG.md**: reset to `# Changelog\n\n## v0.0.0\n\n- Initial scaffold from bborbe/agent-<shape> template via /launch-agent on YYYY-MM-DD`
9. **`agent/.claude/CLAUDE.md`** (if shape has one): adapt the per-agent CLAUDE.md to the new agent's domain
Expand All @@ -129,12 +133,12 @@ Refresh the module graph (in the cloned dir):
rm go.sum && go mod tidy
```

**MANDATORY enforceable check**: invoke the `Task` tool with `subagent_type: 'coding:simple-bash-runner'` to run `cd ~/Documents/workspaces/agent-<name> && make precommit`. This is NOT a documentation suggestion — the skill MUST issue the Task tool call. Without it, the Phase 4 stop-on-failure contract below is unenforceable.
**MANDATORY enforceable check**: invoke the `Task` tool with `subagent_type: 'coding:simple-bash-runner'` to run `cd ~/Documents/workspaces/<name> && make precommit`. This is NOT a documentation suggestion — the skill MUST issue the Task tool call. Without it, the Phase 4 stop-on-failure contract below is unenforceable.

```
Task(
subagent_type: 'coding:simple-bash-runner',
prompt: 'cd ~/Documents/workspaces/agent-<name> && make precommit',
prompt: 'cd ~/Documents/workspaces/<name> && make precommit',
description: 'verify scaffold builds'
)
```
Expand All @@ -149,7 +153,7 @@ Parse the Task result:
Render `references/config-crd-template.yaml` with the captured values into:

```
~/Documents/workspaces/agent-<name>/k8s/agent-<name>-config.yaml
~/Documents/workspaces/<name>/k8s/<name>-config.yaml
```

The Config CRD declares: `assignee`, `image`, `heartbeat`, `taskTypes`, `resources`, `env`, `secretName`, `volumeClaim` (if applicable). Fill from interview answers.
Expand All @@ -162,16 +166,16 @@ Vault root: `~/Documents/Obsidian/Personal/` (resolve via `vault-cli config list

1. **Knowledge page**: render `references/vault-page-template.md` → `50 Knowledge Base/<Name> Agent.md`
2. **Goal**: render `references/goal-template.md` → `23 Goals/Build <Name> Agent.md`
3. **First scenario**: render `references/scenario-template.md` → `~/Documents/workspaces/agent-<name>/scenarios/001-<happy-path-name>.md`
4. **NEXT-DIRECTIONS**: render `references/next-directions-template.md` → `~/Documents/workspaces/agent-<name>/NEXT-DIRECTIONS.md` capturing v1/v2/v3 deferrals surfaced during the interview
3. **First scenario**: render `references/scenario-template.md` → `~/Documents/workspaces/<name>/scenarios/001-<happy-path-name>.md`
4. **NEXT-DIRECTIONS**: render `references/next-directions-template.md` → `~/Documents/workspaces/<name>/NEXT-DIRECTIONS.md` capturing v1/v2/v3 deferrals surfaced during the interview
5. **Agent Hub update**: add row to the "Planned Agents" table in `50 Knowledge Base/Agent Hub.md` (or move existing row to "Production Agents" if the agent was already on the planned list)

## Phase 7 — Commit + push initial state

In the new repo:

```bash
cd ~/Documents/workspaces/agent-<name>
cd ~/Documents/workspaces/<name>
git add -A
git commit -m "scaffold via /launch-agent (template: agent-<shape>, $(date +%Y-%m-%d))"
git push
Expand All @@ -190,7 +194,7 @@ Pattern: `<[A-Z][A-Za-z0-9_+-]*>` — uppercase-leading (matches all our placeho
```bash
# Use $HOME (not quoted ~) — tilde inside quotes is NOT shell-expanded.
grep -rln --include='*.md' --include='*.yaml' --include='*.yml' -E '<[A-Z][A-Za-z0-9_+-]*>' \
$HOME/Documents/workspaces/agent-<name>/ \
$HOME/Documents/workspaces/<name>/ \
"$HOME/Documents/Obsidian/Personal/50 Knowledge Base/<Name> Agent.md" \
"$HOME/Documents/Obsidian/Personal/23 Goals/Build <Name> Agent.md"
```
Expand All @@ -200,16 +204,16 @@ If ANY hit found: HALT with the file paths + offending tokens listed. DO NOT pri
**Only after the leak scan returns empty**, output the numbered checklist (don't execute, just print):

```
🟢 Agent scaffold complete: bborbe/agent-<name>
Repo: https://github.com/bborbe/agent-<name>
🟢 Agent scaffold complete: bborbe/<name>
Repo: https://github.com/bborbe/<name>
Goal: obsidian://open?vault=Personal&file=23%20Goals%2FBuild%20<Name>%20Agent

Next steps (operator decisions):
1. Review the generated Config CRD: ~/Documents/workspaces/agent-<name>/k8s/agent-<name>-config.yaml
1. Review the generated Config CRD: ~/Documents/workspaces/<name>/k8s/<name>-config.yaml
2. Implement domain logic in pkg/factory/factory.go + pkg/prompts/ (template provides scaffolding only)
3. Run `make precommit` locally to verify
4. Build + deploy: `BRANCH=dev make buca`
5. Apply Config CRD: `kubectlquant -n dev apply -f k8s/agent-<name>-config.yaml`
5. Apply Config CRD: `kubectlquant -n dev apply -f k8s/<name>-config.yaml`
6. Run the first scenario: `dark-factory:run-scenario scenarios/001-<happy-path-name>.md`
7. If green, promote to prod: `BRANCH=prod make buca` + apply Config CRD in prod namespace
```
Expand Down Expand Up @@ -247,15 +251,15 @@ When Phase 4's `make precommit` fails (lint error, test failure, security findin

```bash
# Full rollback (run these in order — both are irreversible):
rm -rf ~/Documents/workspaces/agent-<name>
gh repo delete bborbe/agent-<name> --yes
rm -rf ~/Documents/workspaces/<name>
gh repo delete bborbe/<name> --yes
# Vault artifacts were NOT written (Phase 6 is post-Phase-4); nothing to revert there.
```

Then offer the user a choice via `AskUserQuestion`:

1. **Investigate first** (recommended for repeated failures — may be a template bug)
- User runs `cd ~/Documents/workspaces/agent-<name> && git diff` to see the customize changes
- User runs `cd ~/Documents/workspaces/<name> && git diff` to see the customize changes
- User identifies the over-matching sed pattern OR template issue
- Manually revert in the affected file, re-run `make precommit`, continue manually from Phase 5
2. **Rollback + retry** (recommended for typos / wrong shape pick — quickly recoverable)
Expand Down
8 changes: 4 additions & 4 deletions skills/launch-agent/references/config-crd-template.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Config CRD instance for a new agent. Apply with:
# kubectlquant -n <namespace> apply -f k8s/agent-<name>-config.yaml
# kubectlquant -n <namespace> apply -f k8s/<name>-config.yaml
#
# Renders from `/launch-agent` interview answers. Placeholders are <ANGLE_BRACKETED> — replace
# during scaffolding; do NOT leave any in the final output.
Expand All @@ -11,7 +11,7 @@ metadata:
namespace: <NAMESPACE> # dev | prod (typically rolled out to dev first)
spec:
assignee: <NAME> # task frontmatter `assignee: <NAME>` routes here
image: docker.quant.benjamin-borbe.de:443/agent-<NAME>:<BRANCH>
image: docker.quant.benjamin-borbe.de:443/<NAME>:<BRANCH>
heartbeat: 15m # re-spawn interval for in_progress tasks; raise for long-running phases

# task_type values this agent handles (from interview Part 4)
Expand All @@ -32,8 +32,8 @@ spec:
# <KEY>: "<VALUE_OR_TEAMVAULT_LOOKUP_KEY>"

# k8s Secret containing teamvault-resolved creds (from interview Part 7.3)
secretName: agent-<NAME> # see k8s/agent-<NAME>-secret.yaml for the teamvault references
secretName: <NAME> # see k8s/<NAME>-secret.yaml for the teamvault references

# PVC for persistent per-agent state (Claude OAuth, memory, etc.). Omit if stateless.
volumeClaim: <PVC_NAME> # e.g. agent-<NAME>-claude-oauth; remove block if none
volumeClaim: <PVC_NAME> # e.g. <NAME>-claude-oauth; remove block if none
volumeMountPath: /home/claude/.claude
Loading