From 450b58c2a59bc9d3428f49e64f06c450a20d0c94 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Fri, 9 Oct 2026 23:21:57 +0200 Subject: [PATCH] refactor: agents/ moves to ~/Projekte/ai-config .zshrc sources agent-env.zsh from there; README points to the new home. History of the moved files stays here up to e000cfe. Co-Authored-By: Claude Mythos 5.1 --- .zshrc | 2 +- README.md | 11 +- agents/.env.agents | 106 -------------------- agents/AGENTS-claude.md | 75 -------------- agents/AGENTS-opencode.md | 1 - agents/AGENTS.md | 194 ------------------------------------ agents/README.md | 185 ---------------------------------- agents/agent-env.zsh | 18 ---- agents/backend-hint.sh | 11 -- agents/ensure-machine-agent | 50 ---------- 10 files changed, 5 insertions(+), 648 deletions(-) delete mode 100644 agents/.env.agents delete mode 100644 agents/AGENTS-claude.md delete mode 100644 agents/AGENTS-opencode.md delete mode 100644 agents/AGENTS.md delete mode 100644 agents/README.md delete mode 100644 agents/agent-env.zsh delete mode 100755 agents/backend-hint.sh delete mode 100755 agents/ensure-machine-agent diff --git a/.zshrc b/.zshrc index bdd6ee8..084a0da 100644 --- a/.zshrc +++ b/.zshrc @@ -41,4 +41,4 @@ bindkey '^[[B' down-line-or-beginning-search bindkey '^[OA' up-line-or-beginning-search bindkey '^[OB' down-line-or-beginning-search -source "${0:a:h}/agents/agent-env.zsh" +source "$HOME/Projekte/ai-config/agents/agent-env.zsh" diff --git a/README.md b/README.md index 4ecdd42..b635dbc 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ Shared shell and tool configuration. Tracked files: - `.zshrc` — shared zsh UI behavior -- `agents/AGENTS.md` — shared global personal preferences (agent-agnostic) -- `agents/AGENTS-claude.md` — Claude Code wrapper (`@AGENTS.md` + `@~/.claude/RTK.md`) -- `agents/AGENTS-opencode.md` — OpenCode wrapper (`@AGENTS.md`) - `claude/statusline-command.sh` — Claude Code statusline (dir, branch, model, thinking, rate limits) This repo is intentionally small. Machine-specific setup stays local unless explicitly promoted into shared config later. @@ -21,15 +18,15 @@ This repo is intentionally small. Machine-specific setup stays local unless expl source "$HOME/Projekte/dotfiles/.zshrc" ``` -**Claude Code & OpenCode**: symlink the wrappers into each agent's config dir: +**Claude Code**: statusline symlink: ```zsh -ln -s "$HOME/Projekte/dotfiles/agents/AGENTS-claude.md" ~/.claude/CLAUDE.md -ln -s "$HOME/Projekte/dotfiles/agents/AGENTS-opencode.md" ~/.config/opencode/AGENTS.md ln -s "$HOME/Projekte/dotfiles/claude/statusline-command.sh" ~/.claude/statusline-command.sh ``` -The wrappers `@`-include the shared base `agents/AGENTS.md`; add agent-specific content directly in the wrapper file. +Agent instructions, hooks, `.env.agents` and the agent SSH helpers moved to +`~/Projekte/ai-config/agents/` on 2026-10-09 (`.zshrc` sources `agent-env.zsh` from +there); see that folder's README for the wiring. ## What Lives Here diff --git a/agents/.env.agents b/agents/.env.agents deleted file mode 100644 index be4e8af..0000000 --- a/agents/.env.agents +++ /dev/null @@ -1,106 +0,0 @@ -# Python schreibt sofort statt gepuffert: lange Läufe im Hintergrund lassen sich -# zwischendurch im Log mitlesen (sonst bleibt die Datei bis zum Ende leer) -export PYTHONUNBUFFERED=1 - -export USE_AGENT_SSH_USER=1 -# fester Socket des geteilten Agent-ssh-agents (Start + Key-Load macht -# agent-env in ~/.zshrc) — hier gesetzt, damit Signing (GIT_CONFIG_* unten) -# und ssh auch dann auf dem Ein-Key-Agent landen, wenn eine Shell noch eine -# alte agent-env-Funktion ohne Socket-Export geladen hat -export SSH_AUTH_SOCK=/Users/mwiegand/.ssh/isac-agent.sock -export BW_SSH_ARGS="-o User=mwiegand-agent -o IdentitiesOnly=yes -i /Users/mwiegand/.ssh/id_mwiegand_agent.pub -o IdentityAgent=/Users/mwiegand/.ssh/isac-agent.sock" -export BW_SCP_ARGS="$BW_SSH_ARGS" - -# Git-Signing in Agent-Sessions: derselbe Signing-Key wie interaktiv -# ("SSH Signing Key mwiegand"), über den geteilten Maschinen-Agent -# (isac-agent.sock via SSH_AUTH_SOCK). Signatur = "kam von meiner Maschine"; -# wer committet hat, steht im Co-Authored-By-Trailer. GIT_CONFIG_* schlägt -# lokale und globale git-Configs — Overrides in Repos greifen hier nicht. -export GIT_CONFIG_COUNT=2 -export GIT_CONFIG_KEY_0="user.signingkey" -export GIT_CONFIG_VALUE_0="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOxBXKEVpYdw4XBPKEzMDWJqvCy7YyogBaoAo2RnGXQ4" -export GIT_CONFIG_KEY_1="gpg.ssh.program" -export GIT_CONFIG_VALUE_1="ssh-keygen" - -# influxdb -export INFLUXDB_READ_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/mdrtosjm42btdrhejoo4eg4mmu/password" -export INFLUXDB_URL="op://24ylu7osc22gtdcoic6rgjsp2m/mdrtosjm42btdrhejoo4eg4mmu/url" -export INFLUXDB_ORG="op://24ylu7osc22gtdcoic6rgjsp2m/mdrtosjm42btdrhejoo4eg4mmu/org" -# bigquery -export BIGQUERY_SERVICE_ACCOUNT_JSON="op://24ylu7osc22gtdcoic6rgjsp2m/bjyiugpg4gzruvskxjita6eutm/martin-wiegand-ai-50f35be6b554.json" -export BIGQUERY_PROJECT_ID="op://24ylu7osc22gtdcoic6rgjsp2m/bjyiugpg4gzruvskxjita6eutm/project" -# agents guide -export AGENTS_GUID_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/qzwjz73g2evbot5xqqnqtc7tiy/password" -# icinga -export ICINGA_BASE_URL="op://24ylu7osc22gtdcoic6rgjsp2m/stoktfjxvifslqwguhrpmprcwe/url" -export ICINGA_USER="op://24ylu7osc22gtdcoic6rgjsp2m/stoktfjxvifslqwguhrpmprcwe/username" -export ICINGA_API_PASSWORD="op://24ylu7osc22gtdcoic6rgjsp2m/stoktfjxvifslqwguhrpmprcwe/password" -# grafana -export GRAFANA_URL="op://24ylu7osc22gtdcoic6rgjsp2m/jwgq6zd77be4kwvk76q7lskc6i/url" -export GRAFANA_READ_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/jwgq6zd77be4kwvk76q7lskc6i/password" -# ionos -export IONOS_API_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/c67ygbr54xsxblxgnoljeawbse/password" -# swift -export SWIFT3_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/uigw4g52gkd6ocmzeeqi4fk2wu/password" -# bitbucket dc -export BITBUCKET_DC_URL="op://24ylu7osc22gtdcoic6rgjsp2m/wipkwb7pmc2xyuq5vibywhqvpi/hostname" -export BITBUCKET_DC_PAT="op://24ylu7osc22gtdcoic6rgjsp2m/wipkwb7pmc2xyuq5vibywhqvpi/credential" -# bamboo -export BAMBOO_DC_URL="op://24ylu7osc22gtdcoic6rgjsp2m/delniouzyvkndwtkmovv7hhauy/hostname" -export BAMBOO_DC_PAT="op://24ylu7osc22gtdcoic6rgjsp2m/delniouzyvkndwtkmovv7hhauy/credential" -# atlassian cloud -export ATLASSIAN_CLOUD_PAT="op://24ylu7osc22gtdcoic6rgjsp2m/ywanqqqcviwqyoeoxa6g2au5ki/credential" -export JIRA_READONLY_PAT="op://24ylu7osc22gtdcoic6rgjsp2m/qqi25p2cuwkjkhbxj7spbjq2fe/credential" -# n8n prod -# "mcp": { -# "n8n-seibert": { -# "type": "local", -# "command": [ -# "npx", -# "-y", -# "n8n-mcp" -# ], -# "environment": { -# "N8N_API_KEY": "{env:N8N_SEIBERT_API_KEY}", -# "N8N_API_URL": "{env:N8N_SEIBERT_API_URL}" -# } -# } -# } -export N8N_SEIBERT_API_URL="op://24ylu7osc22gtdcoic6rgjsp2m/qbkhrul2lm7uxy4kjqjpt5eoju/url" -export N8N_SEIBERT_API_KEY="op://24ylu7osc22gtdcoic6rgjsp2m/qbkhrul2lm7uxy4kjqjpt5eoju/credential" -# forgejo test token -export FORGEJO_URL="op://24ylu7osc22gtdcoic6rgjsp2m/zdkxvnabf23xjnp3lwti43hmma/hostname" -export FORGEJO_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/zdkxvnabf23xjnp3lwti43hmma/credential" - -# proxmox -export PROXMOX_URL_1="op://24ylu7osc22gtdcoic6rgjsp2m/xqpmcgcvytlmptfwwpn7kui6ei/url" -export PROXMOX_USER_1="op://24ylu7osc22gtdcoic6rgjsp2m/xqpmcgcvytlmptfwwpn7kui6ei/username" -export PROXMOX_TOKEN_1="op://24ylu7osc22gtdcoic6rgjsp2m/xqpmcgcvytlmptfwwpn7kui6ei/credential" - -export PROXMOX_URL_2="op://24ylu7osc22gtdcoic6rgjsp2m/hiax7cmpeezanve2orhstjwchi/url" -export PROXMOX_USER_2="op://24ylu7osc22gtdcoic6rgjsp2m/hiax7cmpeezanve2orhstjwchi/username" -export PROXMOX_TOKEN_2="op://24ylu7osc22gtdcoic6rgjsp2m/hiax7cmpeezanve2orhstjwchi/credential" - -# ai -export VLLM_API_KEY="op://24ylu7osc22gtdcoic6rgjsp2m/ryeegxxtakfolgqqgnwtdio2ny/credential" -export GOOGLE_AI_STUDIO_API_KEY="op://24ylu7osc22gtdcoic6rgjsp2m/e225knzbmb6rxe5utr2csjphqu/credential" -export CLAUDE_VERTEX_GATEWAY_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/gwkninymrh2a7msvsou24c3zvu/credential" -export ONYX_ADMIN_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/rw6ccrvzirrls47g4vkjurhzm4/credential" - -# elewom -export ELEWOM_AUTH_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/zwznfforzil5yrjrl77uuo67pi/credential" - -# verda -export VERDA_INFERENCE_KEY="op://24ylu7osc22gtdcoic6rgjsp2m/sxnfz2logzqe47d4yilfh5whly/credential" - -# seatable -export SEATABLE_ACCOUNT_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/smfdkpo4ve7pfeabnoddls72hu/credential" - -# bitbucket cloud seibert media workspace -export BITBUCKET_CLOUD_SEIBERT_WORKSPACE="op://24ylu7osc22gtdcoic6rgjsp2m/6vtqs6biiegyhi2z5i24u4nav4/credential" - -# netbox -export NETBOX_AUTH_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/5gzmvpudiuv6tdzi6amzsbkwcu/credential" - -# sentry -export SENTRY_READ_TOKEN="op://24ylu7osc22gtdcoic6rgjsp2m/v3vxfekfbkenbz7hxaqjai4q4m/credential" diff --git a/agents/AGENTS-claude.md b/agents/AGENTS-claude.md deleted file mode 100644 index 2be0f12..0000000 --- a/agents/AGENTS-claude.md +++ /dev/null @@ -1,75 +0,0 @@ -@./AGENTS.md - -## Company knowledge (onyx & thicket) - -- Company-knowledge questions go to the `onyx` skill first (its description - says when); its index surfaces pages that direct CQL/JQL misses. Always cite - source links. -- Two shortcuts past onyx: Thicket MCP for people lookups (role, team, - email — its other sections are thin), Confluence CQL for precise - lookups with known space/title. -- Same-topic follow-ups: use `onyx answer "..." --session `. - -## Subagents - -**Default to delegating whenever the expected tool output exceeds the -conclusion the main thread actually needs** — multi-file research, log/data -analysis, doc review, broad searches, verification runs. The session context -is the scarce resource; a subagent burns its own context and returns only the -result. Work inline only for single-fact lookups where file and symbol are -already known — there, a subagent is pure overhead. - -Use the `Agent` tool. Prefer specialized agent types over `general-purpose` -when one fits: - -- **`Explore`** — read-only search: *locates* code and facts (reads excerpts, - not whole files). It does not review or audit; quality judgments stay with - the parent or a dedicated reviewer. -- **`Plan`** — implementation strategy, architecture trade-offs. -- **`fork`** (`subagent_type: "fork"`) — inherits the full conversation - context, runs in the background, keeps its tool output out of the main - thread. The tool of choice for "do X with everything you already know, give - me only the outcome" — no need to re-explain state in the prompt. -- Agent types not listed as available in the session (e.g. a `code-reviewer`) - don't exist there — check the available-types list instead of guessing. - -### Behavioral tier (MANDATORY — always set) - -Prefix every `Agent` prompt with one of: - -- `[TIER: FAST]` — return raw results immediately; no internal monologue, no synthesis -- `[TIER: STANDARD]` — focused implementation logic; minimal preamble -- `[TIER: DEEP]` — complex reasoning; must conclude with a summary - -### Model selection (relative to the parent, no fixed names) - -Resolution order: explicit `model` param → agent type frontmatter → parent -inheritance. (`fork` always runs on the parent model; an override is ignored.) -Model names shift (Opus was the top tier when this was written; Fable and -Mythos sit above it now), so think in tiers, not names: - -0. **Self-hosted backend (a SessionStart hook says so when `ANTHROPIC_BASE_URL` - points at the vLLM gateway): never pass `model`.** Aliases do not exist - there; every subagent inherits the one available model. -1. **Specialized agent types** (`Explore`, `Plan`, …) keep their frontmatter - model. Leave `model` off. -2. **Routine `general-purpose` work** (mechanical edits, bulk lookups, - verification runs): one tier below the parent, today `sonnet`. Don't pay - top rates for routine work. -3. **Judgement work** (research, recommendation, trade-off, review, blinded - eval judges): the parent's tier, so pass no `model` and let it inherit. - For "how should X look" questions discuss options first, do not build. -4. **`haiku`** only for purely mechanical tasks: bulk classification, format - conversion, summarizing pre-filtered text. Code-semantic work is at least - `sonnet`. - -### Context hygiene - -- Prefer parallel `Explore` calls over sequential `general-purpose` sessions; - spawn independent agents in a single message so they run concurrently. -- Once delegated, don't duplicate: never run the same search yourself while - an agent is on it, and don't poll — results arrive as notifications. -- Follow-ups to an existing agent go via `SendMessage` (keeps its context); - a new `Agent` call always starts fresh — right for unrelated lookups. -- Summarize subagent findings in the main thread; the raw report is not - shown to the user, so relay what matters. diff --git a/agents/AGENTS-opencode.md b/agents/AGENTS-opencode.md deleted file mode 100644 index 285e0f5..0000000 --- a/agents/AGENTS-opencode.md +++ /dev/null @@ -1 +0,0 @@ -@./AGENTS.md diff --git a/agents/AGENTS.md b/agents/AGENTS.md deleted file mode 100644 index 249bf7d..0000000 --- a/agents/AGENTS.md +++ /dev/null @@ -1,194 +0,0 @@ -# Personal preferences - -## Workflow - -- **Specs, plans, and design docs go into the repo they describe.** When - brainstorming, plan-mode, or any flow produces a persistent document - (design spec, implementation plan, ADR, etc.), the final destination is - always the working tree of the project it belongs to — typically - under `docs/` (or wherever the project conventionally keeps them). - Scratch locations like `~/.claude/plans/.md` are fine *during* - plan mode, but the moment plan mode exits with an approved artifact, - copy it into the repo and `git commit` it. Don't ship a feature - while its spec or plan still lives only in a scratch file. If a - project's `AGENTS.md` / `CLAUDE.md` specifies a different directory, - that wins. - -## Identity - -- Email martin.wiegand@seibert.group, Bitbucket username `mwiegand`. - -## Write actions: confirm before executing - -Before any action that creates, modifies or sends something outside this -machine (Jira comments, ticket transitions, Confluence edits, chat messages, -emails), show exactly what will happen (the text, the target, the transition) -and wait for explicit approval. For bulk operations present the full list -first and change nothing until the plan is confirmed. Local edits, commits, -pushes and memory writes do not need this. - -## External communication (messages, emails, comments) - -- Neutral and technical: the agent conveys information, it does not imitate - a human. No social emojis, no emotional filler ("Danke dir! 🙂", "Huhu"). -- Structure: factual salutation ("Hallo ,"), precise content, concrete - call to action, closing without emotional markers. -- Append "(ai generated)" to every external text I phrase; never in chat - replies to me, and not in automated webhook or alert texts. -- PR thread replies carry only the decisive argument: no side findings, - links or follow-up ideas. -- Times in chat in Europe/Berlin local time; convert server UTC first. - -## Error handling - -If a tool call fails or hits a rate limit, pause and tell me. One retry with -the identified cause fixed (e.g. a sandbox denial) is fine; never retry in a -loop or brute-force through API calls. - -## Git commits - -- Conventional Commits; imperative, intent-focused subjects; body only when - needed; split unrelated changes; ask only for mixed diffs. -- On a Jira issue, prefix the subject with its key (`IT-12345`) in every repo. - The ISAC repo has its own single-line convention (`IT-12345 bw/: - `, no Conventional Commits); the `isac` skill has the details. -- **Push and open PRs (also drafts) without asking**, then report the link. - Not covered: force-push, pushes to shared or foreign branches, merges, PR - comments. Exception: in the private l4d2 project Martin pushes himself. -- **Never `--amend` and never `git stash pop` when other sessions may work in - the same repo.** An amend rewrote another session's pushed commit once - (2026-09-25); a pop after "No local changes to save" pops a foreign stash, - in the ISAC repo one with staged secrets. Fix-ups are new commits; to look - at an old version use `git show :`. -- **Commit messages via `-F` use one absolute path for writing and reading** - (the Bash tool's cwd changes silently; a relative `.tmp/commitmsg.txt` once - landed in a task folder and the commit took the old message). Check - `head -1` before committing; several commits per turn get numbered files. -- **Own PR branches: readable over historical.** Re-cut commits into one per - logical step (pure `git mv` first, content second) and push with - `--force-with-lease`; rebuild from the final tree instead of amending. - Bitbucket detects renames only by content similarity, so keep a renamed - file small. -- `git push` over SSH fails inside the sandbox (no raw TCP; `excludedCommands` - does not help): push with `dangerouslyDisableSandbox: true`, ISAC via - `isac git -C `. - -## Jira - -- Search with JQL scoped to issues, never Confluence. Default filter: - `assignee = currentUser() AND status NOT IN (Done, Closed) ORDER BY duedate ASC` -- Asked to work on a ticket: present the ticket, then a numbered action plan. - No vague follow-up questions. -- Write actions (tickets, comments) go through the `jira-write` skill. - Comments are internal by default; public only when they address the - participants, never silently public. - -## Code health - -The `coding` skill carries the rules (act on cleanups, scope boundary, rule of -three, friction as architectural data, per-language style). Load it before -editing code if it did not trigger on its own. - -## Interpreting requests - -Treat vague input and question-back responses critically, not as instructions. - -- **Vagueness is not authorization.** "Make it cleaner", "maybe X?", - "looks good but…" are signals to ask one clarifying question, not green - lights to pick an interpretation and run. -- **A tangent is not an answer.** If you ask "A or B?" and get "maybe X?", - that's a new question, not a pick. Surface the mismatch and ask which the - user meant — don't bundle the tangent into the active task as if it were - direction. - -## Team memory (cognee) - -cognee is a real memory, not an archive: unlike `MEMORY.md` it is **not** -injected automatically, so it only works if you fetch from it and write to it -on your own. Command forms, flags, dataset sharing and failure modes are in the -`cognee` plugin skill; this section only says *when*. - -- **Read: `cognee recall ` before the first tool call** when the task - touches infrastructure, a server, a service, a ticket or a tool we run - ourselves; when an error "smells familiar"; when choosing between options - where a decision may already exist; or when the task will plausibly take more - than a handful of tool calls. Plain `recall` reads every readable dataset, - team included. Treat what comes back like a colleague's note: useful, possibly - outdated; verify a named file, flag or host still exists before acting on it. -- **Write: `cognee remember` as the insight appears**, not at session end. - Announce it in one line, never ask first: memory writes are exempt from any - project rule that asks to confirm write actions (those rules target external - systems: Jira, chat, Confluence, hosts). Moments: a bug fixed whose cause was - not visible in the code; a decision with a rejected alternative (save the - *why*); a measurement that contradicts an assumption; a correction, yours or - mine; a tool or API that behaves differently than its docs claim. -- **Routing:** `--team` for everything concerning team IT (Seibert IT-Infra, - ISAC/bw, monitoring, INFRA board, shared services, team processes), plain - `remember` for my preferences and personal context. Never secrets, - credentials or customer-personal data; nothing the repo already records; - nothing that only matters inside this conversation. -- **Projects with their own findings convention** (task folders, - `erkenntnisse/`, `decisions/`): write the file there first, then the cognee - note with the conclusion and a link to the file. The file is the detailed - record; cognee is what the next session or a colleague in another repo finds. -- **Hygiene:** on recurring topics `recall` first and extend the existing note - instead of adding a second one. Write notes that answer a question: concrete - numbers, the error message verbatim, the fix, and the external references - (Confluence, Jira, TeamVault, Google Chat thread URLs - `https://chat.google.com/room//`). No size limit, so - write self-contained. - -## Sandbox - -- **Always run commands sandboxed first.** Only use `dangerouslyDisableSandbox: true` after a sandbox-related failure — never preemptively. One targeted retry with the identified cause fixed is fine; retrying in a loop is not. -- **On sandbox failure:** analyze the error message for the blocked path, then suggest adding it to `sandbox.filesystem.allowWrite` in `~/.claude/settings.json`. Only fall back to `dangerouslyDisableSandbox: true` if the path can't be determined or the user prefers it. -- **Commands that must run unsandboxed belong in `sandbox.excludedCommands`** (e.g. `git *`, the pinned - `bin/srv`-style wrappers), not behind the flag: the exclusion is the owner's decision, the flag is the - model's. Without a hook, the flag is only a convention: an unsandboxed command that matches an allow - rule (`Bash(git *)`, `Bash(pytest *)` …) runs without a prompt in default mode, and auto mode may wave it - through as well. (The former PreToolUse hook that forced a prompt on the flag was removed 2026-10-04; - its JSON is in this file's git history.) - -## Temporary Files - -- **Use `$TMPDIR` or `.tmp/` (project-local) for temporary files.** Never use `/tmp` directly or paths like `/Library/Application Support` — those are not in the sandbox allowlist and trigger permission prompts. -- `$TMPDIR` is set by the sandbox to a writable path. `.tmp/` inside the project directory is always writable without prompts. -- Ensure `.tmp/` is listed in `.gitignore` when creating temp files in a tracked repo. - -## Shell — literal `!` in Bash commands - -- **Never put a literal `!` in an inline Bash command.** Claude Code's Bash tool - escapes every `!` to `\!` at the transport layer before any shell sees it — - even inside single/double quotes and in non-interactive shells - (anthropics/claude-code#61121, a regression of a fix shipped in 2.1.87). The - stray backslash corrupts downstream tools: Python `!=` → SyntaxWarning + broken - string, jq, Jira JQL (`status != Done` → server 400), branch names, etc. - `bash -c '...'` and quoted heredocs do **not** avoid it. -- **One rule covers everything:** if a command needs a `!`, write the command/code - to a file with the Write tool under `.tmp/` and run the file. `.tmp/` is - prompt-free via the `Edit(.tmp/**)` allow rule in `~/.claude/settings.json`. Do - **not** create the file with inline `printf`/`echo >` — that re-escapes the `!`. -- Need a literal `!` inline anyway: ANSI-C hex `$'\x21'` (e.g. `B=$'\x21'; cmd - "${B}=…"`) yields a real `!` with no literal `!` in the command. - -## Tooling - -- **Sourcebot for Seibert repos without a local checkout:** the `sourcebot` skill - (MCP tools, gotchas such as mirrors and result caps; never `ask_codebase` on - your own). A local checkout stays first choice. -- **`ccc` before `grep` for concept questions.** In a repo with a - `.cocoindex_code/` directory, a question about a concept ("where is X - handled", "what talks to Y", "which code does Z") starts with - `ccc search ''` from the project root, then `grep` verifies what it - found. An exact string (UUID, ticket ID, error message, identifier) goes - straight to `grep`/`rg`: ccc is embeddings only and will not find it. Gotchas - in the `ccc` skill. -- **`ccc index` / `ccc init` are read-safe maintenance.** They only write to the - gitignored `.cocoindex_code/` directory, never to source. Run them without - confirmation prompts; treat like `grep` for permission purposes. -- **`md` for long Markdown files instead of `head`/`grep -n '^#'`.** - `md outline ` prints the heading tree with line numbers and size per - section; decide from that, then `md section ""` for exactly - that subtree (`--range` yields `offset limit` for the Read tool, `--depth 0` - cuts subsections). It knows code fences and frontmatter, so a `# comment` in - a bash block is not a heading. Below ~500 lines just `Read` the file. diff --git a/agents/README.md b/agents/README.md deleted file mode 100644 index a0b8b47..0000000 --- a/agents/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# Design: SSH-Key- und Git-Signing-Architektur (Arbeitsrechner mwiegand) - -Stand 2026-08-04 — umgesetzt und verifiziert. Beschreibt das *Warum* hinter -dem Code in diesem Verzeichnis (`agent-env.zsh`, `ensure-machine-agent`, -`.env.agents`) plus `~/.ssh/config` und `~/.gitconfig`. - -Umsetzungsstand, Historie und die offenen Schritte auf ISAC-Seite stehen im -ai-Repo: `tasks/2026-07-31-agent-key-isolation/HANDOFF.md`, Vorarbeit in -`tasks/2026-06-13-isac-infra-agent-isolation/`. - -## Problem - -Auf dem Rechner laufen zwei Arten von Akteuren mit unterschiedlichem -Vertrauensniveau: - -- **Ich** (interaktive Shell) — brauche den persönlichen SSH-Key, der über - LDAP auch auf **Kundensystemen** autorisiert ist. -- **Agents** (Claude/OpenCode via `agent-env`) — sollen interne Systeme - erreichen, aber **strukturell nicht unbemerkt** an den kundenfähigen Key - kommen. - -Vor dem Umbau war die Trennung rein konventioniert: der private Haupt-Key lag -unverschlüsselt als `~/.ssh/id_mwiegand` auf der Platte (für jede Agent-Session -lesbar), der 1Password-SSH-Agent bot alle Keys jedem Prozess an, und ein -Login-Script lud den Haupt-Key zusätzlich in den Apple-Agent. Git-Signing hing -am Datei-Key. - -Dazu die Komfort-Anforderung: **Signieren darf im Alltag nicht nerven** — -keine Fingerabdruck-Freigabe pro Commit. - -## Anforderungen - -1. Agents verwenden einen eigenen SSH-Key (nur interne Systeme); der - kundenfähige Haupt-Key ist für sie nicht still nutzbar. -2. Jede Nutzung des Haupt-Keys erzeugt eine sichtbare, bewusste Freigabe - (Fingerabdruck) — der Prompt ist die Schranke. -3. Kein privater Schlüssel liegt als Datei auf der Platte. -4. Git-Commits sind signiert, prompt-frei — Herkunft „diese Maschine" ist - nachweisbar (Mensch/Agent unterscheiden die Commit-Trailer). -5. Kein Autoload beim Login (explizite Entscheidung): was in einem Agent - liegt, wurde bewusst dorthin gelegt. - -## Architektur: drei Schlüssel, drei Wege - -| Schlüssel (1Password-Item, Vault Employee) | Fingerprint | Lebt in | Zweck | Prompt-Verhalten | -|---|---|---|---|---| -| **SSH Key mwiegand** (Haupt-Key) | `SHA256:lIx1Na…` | nur 1Password | SSH überall, inkl. Kundensysteme | 1Password-Freigabe pro Key und Terminal-Session — bewusst spürbar | -| **SSH isac agent** (Agent-Key) | `SHA256:77FiJM…` | geteilter Maschinen-ssh-agent `~/.ssh/isac-agent.sock` | SSH auf interne Systeme (User `mwiegand-agent`) | einmal `op read` pro Boot, danach still | -| **SSH Signing Key mwiegand** (Signing-Key) | `SHA256:SvHSJQ…` | geteilter Maschinen-ssh-agent `~/.ssh/isac-agent.sock` | Git-Signing **aller** Commits der Maschine — meine und Agent-Commits | einmal `ensure-machine-agent` pro Boot (passiert automatisch mit der ersten Agent-Session), danach still | - -Die Auswahl des Schlüssels erfolgt **über den Agent-Socket**, nicht über -Key-Dateien: - -- `~/.ssh/config` `Host *` → `IdentityAgent ~/.1password/agent.sock` - (Haupt-Key, promptet pro Nutzung). -- `Match exec USE_AGENT_SSH_USER=1` → `User mwiegand-agent` + - `IdentityAgent ~/.ssh/isac-agent.sock` (Ein-Key-Agent; steht vor `Host *`, - nach den ckn-Host-Blöcken). -- Git-Signing läuft über `SSH_AUTH_SOCK`, der überall (interaktive Shell via - `agent-env.zsh`, Agent-Sessions zusätzlich via `.env.agents`) auf den - Maschinen-Agent zeigt — signiert wird immer mit dem Signing-Key. Fürs - interaktive SSH ist `SSH_AUTH_SOCK` irrelevant, weil die `ssh_config` - `IdentityAgent` explizit setzt. - -Der Sicherheitskern: **die Prompt-Semantik bleibt sauber.** Ein -1Password-Prompt bedeutet immer „jemand will den kundenfähigen Key" — er -kommt selten und ist ernst zu nehmen. Signing und Agent-SSH erzeugen nie -Prompts, also gibt es keine Gewöhnung an reflexhaftes Bestätigen. - -## Komponenten - -Alles Agent-bezogene liegt versioniert in diesem Verzeichnis; `~/.zshrc` und -`~/.local/bin` enthalten nur noch Verweise darauf. - -| Datei | Rolle | -|---|---| -| `agents/agent-env.zsh` (gesourced von `dotfiles/.zshrc`) | exportiert `SSH_AUTH_SOCK` fest auf `~/.ssh/isac-agent.sock`; `agent-env()` ruft `ensure-machine-agent` und dann `op run --env-file ~/.env.agents` | -| `agents/ensure-machine-agent` (Symlink in `~/.local/bin/`) | idempotent: startet den Maschinen-Agent bei Bedarf und stellt **beide** Keys sicher (Signing + Agent). Wird von `agent-env` gerufen; manuell nur nötig, wenn man nach einem Reboot committen will, bevor die erste Agent-Session lief | -| `agents/.env.agents` (Symlink `~/.env.agents`) | rein deklarativ — `op run` parst die Datei nur, führt keinen Code aus: `USE_AGENT_SSH_USER=1`, `SSH_AUTH_SOCK` (Robustheit: Konsumenten ohne `agent-env` finden den richtigen Socket), `BW_SSH_ARGS`, `GIT_CONFIG_*` → `user.signingkey` = Signing-Pubkey (Env schlägt alle git-Configs, auch lokale), Secret-Referenzen | -| `~/.ssh/config` | Socket-Routing s. o.; keine `IdentityFile`-Einträge für persönliche Keys | -| `~/.gitconfig` | `gpg.format=ssh`, `commit.gpgsign=true`, `user.signingkey` = Signing-Pubkey-String (kein Pfad), `gpg.ssh.allowedSignersFile` | -| `~/.ssh/allowed_signers` | beide Pubkeys (Signing + Agent) für lokale Verifikation (`git log --format=%G?`) | - -Gelöscht: `~/.ssh/id_mwiegand` (unverschlüsselter Datei-Key), -`add-ssh-key-to-agent` (Login-Autoload des Haupt-Keys), lokale -`user.signingkey`-Overrides in 8 Repos. - -## Entscheidungen und verworfene Alternativen - -**Signing-Key als eigener Schlüssel statt Haupt-Key.** Die Prompt-Frequenz -hängt nicht am Schlüssel, sondern am Mechanismus — aber ein gemeinsamer -Schlüssel hätte Haupt-Key-Prompts beim Committen alltäglich gemacht und die -Schranke durch Gewöhnung entwertet. Außerdem: Forges kennen den Signing-Key -nur als Signaturschlüssel; ein Leak ist kein Systemzugriff. - -**Ein gemeinsamer Signing-Key für Mensch und Agents** (statt Agent-Commits -mit dem Agent-SSH-Key zu signieren, wie zuerst gebaut). Die Key-Trennung -hätte nur *ehrliche* Agent-Commits von meinen unterschieden: die -Agent-Sockets sind für jeden lokalen Prozess erreichbar, ein böswilliger -Agent könnte ohnehin als ich signieren. Für ehrliche Commits leistet der -`Co-Authored-By`-Trailer die Unterscheidung. Die Signatur bedeutet ehrlich -„kam von meiner Maschine"; dafür halbiert sich die Forge-Pflege (ein Key -registrieren/verifizieren statt zwei) und der Boot-Load läuft automatisch -über die erste Agent-Session mit. Ältere Commits (2026-08-04) sind noch mit -dem Agent-Key signiert — er bleibt dafür in `allowed_signers`. - -**`op-ssh-sign` erwogen und verworfen.** Zuerst umgesetzt (Key verlässt -1Password nie), dann zurückgebaut: die 1Password-Autorisierung gilt global -pro App(+Terminal-Session) für *alle* Keys. Das Signing-Nerv-Problem hätte -sich nur durch Lockern des Modells lösen lassen — was die Haupt-Key-Schranke -mitgelockert hätte. Der Agent-Weg entkoppelt beides: Signing prompt-frei, -1Password-Modell bleibt streng. (Zwischenschritt Apple-Agent als -Signing-Halter wurde durch den Maschinen-Agent ersetzt — ein Agent weniger.) - -**macOS-Keychain als Signer verworfen.** Die Keychain speichert Bytes, kann -aber keine SSH-Signaturen erzeugen (`security` bietet nur `cms`). Jeder -Keychain-Weg endet in einer Klartext-Tempdatei pro Commit — schlechter als -jeder Agent. (`ssh-add --apple-use-keychain` speichert nur Passphrasen und -setzt eine Key-Datei voraus.) - -**Kein Login-Autoload.** Bewusste Entscheidung: Keys werden nicht beim Login -geladen, sondern lazy — mit der ersten Agent-Session (automatisch) bzw. per -`ensure-machine-agent` von Hand. Symptom eines fehlenden Loads ist ein -Commit-Fehler `Couldn't find key in agent?` — eindeutig und folgenlos. - -**Geteilter Agent statt Agent pro Session.** Ein fester Socket -(`~/.ssh/isac-agent.sock`) statt mktemp pro Session: ein `op read` pro Key -und Boot statt pro Session, kein Cleanup verwaister Agents, und der -Match-Block kann auf einen stabilen Pfad zeigen (wirkt auch für Subprozesse -mit bereinigtem Env). Beide Bewohner (Agent-Key, Signing-Key) sind per -Definition die Schlüssel, die Agents zugänglich sein dürfen — Langlebigkeit -weicht nichts auf. - -**`SSH_AUTH_SOCK` zusätzlich fest in `.env.agents`.** Lektion aus dem Umbau: -eine Shell mit veralteter `agent-env`-Funktion lud die neue `.env.agents` -(Signing-Config auf Agent-Key), zeigte aber noch auf den leeren Apple-Agent → -`Couldn't find key in agent?`. Signing-Config und Socket dürfen nicht -auseinanderfallen; beide stehen jetzt in derselben Datei. - -## Grenzen (bewusst offen) - -Die Architektur ist eine **Zustimmungs- und Erkennungsschranke, keine -technische Unerreichbarkeit**: - -- Der 1Password-Socket bleibt für Agent-Prozesse erreichbar - (`allowAllUnixSockets: true` in der Claude-Sandbox-Config; 1Password kann - Keys nicht pro Client filtern). Ein Agent, der den Haupt-Key anfordert, - löst aber einen sichtbaren Prompt aus, den ich ablehnen kann. -- **`op`-CLI-Pfad** (empirisch geprüft 2026-08-04): ein Agent kann `op read` - auf beliebige Items *versuchen* — u. a. den Haupt-Key — aber nicht - unbemerkt. Vier Versuche aus einer Agent-Session ergaben vier - 1Password-Dialoge; die Freigabe für das `op run` beim `agent-env`-Start - deckt nachgelagerte `op`-Aufrufe **nicht** mit ab (ob eine *bestätigte* - Freigabe danach ein 10-Minuten-Fenster öffnet, ist unverifiziert — der - Dialog behauptet das, gemessen wurde es nicht). - Die verbleibende Schwäche ist die **Blindheit des Dialogs**: er sagt „full - access to your account", nennt aber nicht das angefragte Item. Beim - Bestätigen ist nicht unterscheidbar, ob ein Agent ein harmloses - API-Passwort oder den kundenfähigen SSH-Key liest. Das ist systematisch, - nicht nur eine Eigenschaft offener Abfragen: auch `op read` mit einer - punktgenauen `op://Employee//`-Referenz zeigt denselben - Dialog (geprüft 2026-08-04) — 1Password autorisiert das Werkzeug, nie den - Zugriff, selbst wenn dieser vollständig spezifiziert ist. Gerade die - Secrets in `.env.agents` sind ausschließlich solche Referenzen — und weil legitime - Agent-Starts denselben Dialog erzeugen, trainiert der Alltag reflexhaftes - Bestätigen. Genau das macht der gescopte Service-Account (Schritt 1) - überflüssig: was nicht anfragbar ist, muss nicht beurteilt werden. -- Vollständige Schließung = HANDOFF-Schritte 1/3: gescopter Service-Account - mit eigenem Vault für den Agent-Key, Socket-Sperre in der Sandbox, - Seatbelt-Variante für unsandboxte Kommandos. - -## Betrieb - -- **Nach Reboot:** die erste `agent-env`-Session lädt beide Keys automatisch; - nur wer vorher committen will, ruft einmal `ensure-machine-agent` (Symptom - sonst: `Couldn't find key in agent?`). -- **Verifikation:** `git log --format='%h %G? %GK'` — `G` + `SvHSJQ…` für - alle Maschinen-Commits (Commits vom 2026-08-04 noch mit `77FiJM…`). - `ssh-add -l` in einer Agent-Session zeigt genau die zwei Maschinen-Keys. -- **Forges:** der Signing-Pubkey muss bei Bitbucket DC/Forgejo als *Signing* - Key registriert sein; bei Forgejo zusätzlich ownership-verifiziert - (Challenge signieren, nur via Web-UI), sonst „unverified" (offen). -- **Rotation:** neuer Key = neues 1Password-Item + Pubkey in - `~/.gitconfig`/`allowed_signers`/`.env.agents` bzw. ISAC - (`admin_agent_users`) nachziehen; es gibt keine Key-Dateien zu tauschen. diff --git a/agents/agent-env.zsh b/agents/agent-env.zsh deleted file mode 100644 index 625fd97..0000000 --- a/agents/agent-env.zsh +++ /dev/null @@ -1,18 +0,0 @@ -# Agent-Sessions (Claude, OpenCode): Secrets aus 1Password und SSH-Identität -# des Agent-Users. Wird von dotfiles/.zshrc gesourced. -# Architektur und Begründungen: agents/README.md. - -# Git-Signing und ssh-add greifen über SSH_AUTH_SOCK auf den geteilten -# Maschinen-Agent zu — interaktiv wie in Agent-Sessions. Fürs interaktive SSH -# ist die Variable irrelevant, weil ~/.ssh/config IdentityAgent explizit setzt -# (Haupt-Key via 1Password, Agent-User via diesem Socket). -export SSH_AUTH_SOCK="$HOME/.ssh/isac-agent.sock" - -agent-env() { - # Agent + beide Maschinen-Keys sicherstellen; der op-read darin teilt sich - # die 1Password-Autorisierung mit dem op-run unten, also ein Prompt. - ensure-machine-agent || return 1 - - op run --account WHZOJQOJPZHTFPJC6JZAOCUCBI \ - --env-file ~/.env.agents --no-masking -- "$@" -} diff --git a/agents/backend-hint.sh b/agents/backend-hint.sh deleted file mode 100755 index 8e368f7..0000000 --- a/agents/backend-hint.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# SessionStart hook: tell the agent when the session runs against the self-hosted -# vLLM gateway, where model aliases (sonnet, opus, haiku) do not exist. -# Prints nothing on Anthropic/Vertex so the default rules apply. -case "${ANTHROPIC_BASE_URL:-}" in - *vllm*|*seibert.tools*) - model="${ANTHROPIC_MODEL:-${CLAUDE_CODE_SUBAGENT_MODEL:-the configured model}}" - printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Self-hosted backend (%s): only %s is available. Never pass a model override to Agent calls; all subagents inherit this model."}}\n' "$ANTHROPIC_BASE_URL" "$model" - ;; -esac -exit 0 diff --git a/agents/ensure-machine-agent b/agents/ensure-machine-agent deleted file mode 100755 index a1d4632..0000000 --- a/agents/ensure-machine-agent +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# Stellt den geteilten Maschinen-ssh-agent sicher: Socket läuft und hält beide -# Keys, die auf dieser Maschine gebraucht werden. Idempotent — mehrfach -# aufrufbar, macht nur was fehlt. -# -# SSH isac agent SSH auf interne Systeme (User mwiegand-agent) -# SSH Signing Key Git-Signing, für mich UND Agent-Sessions -# -# Wird von agent-env bei jedem Start gerufen; manuell nur nötig, wenn man nach -# einem Reboot committen will, bevor die erste Agent-Session lief (Symptom: -# "error: Couldn't find key in agent?"). -# -# Der kundenfähige Haupt-Key ("SSH Key mwiegand") gehört bewusst NICHT hierher: -# er lebt nur hinter der 1Password-Freigabe (~/.1password/agent.sock, siehe -# Host * in ~/.ssh/config), damit Agents ihn nicht unbemerkt nutzen können. -# Architektur und Begründungen: agents/README.md im dotfiles-Repo. - -PATH="/opt/homebrew/bin:/usr/bin:/bin:$PATH" -SOCK="$HOME/.ssh/isac-agent.sock" -OP_ACCOUNT="WHZOJQOJPZHTFPJC6JZAOCUCBI" -export SSH_AUTH_SOCK="$SOCK" - -# Agent tot oder nie gestartet? (ssh-add exit 2 = Socket antwortet nicht) -ssh-add -l >/dev/null 2>&1 -if [ $? -eq 2 ]; then - rm -f "$SOCK" - /usr/bin/ssh-agent -a "$SOCK" >/dev/null || exit 1 -fi - -# $1 = Fingerprint (schon geladen?), $2 = op-Referenz, $3 = Anzeigename -ensure_key() { - if ssh-add -l 2>/dev/null | grep -q "$1"; then - return 0 - fi - if op read --account "$OP_ACCOUNT" "$2" | ssh-add -q -; then - echo "$3 geladen (bis zum nächsten Reboot)." >&2 - else - echo "$3 konnte nicht geladen werden (1Password gesperrt?)" >&2 - return 1 - fi -} - -rc=0 -ensure_key "SHA256:SvHSJQcHlAWUmF8EnflNsrv0mhWGARvAKLRPSBQ460I" \ - "op://Employee/37z2tbpilrjrgyrhckb5cbrhsq/private key?ssh-format=openssh" \ - "Signing-Key" || rc=1 -ensure_key "SHA256:77FiJM6Z1eN7Ka7YpoCKIn9xVJFQUj6nBQa/g1rFya4" \ - "op://Employee/ec2mtn4kkxnypyc4gxutg7lajq/private key?ssh-format=openssh" \ - "Agent-Key" || rc=1 -exit $rc