# AGENTS.md — GLOBAL HOUSE RULES Loaded by EVERY crush session (all lanes, all repos) via crush's global context path (~/.config/AGENTS.md -> symlink into the TSYSGroupAIOS repo, the tracked home). Machine-consumable distillation of TSYSGroupAIOS/BASELINE-PROMPT.md + workspace rulings; the human docs stay canonical. Layering: THIS FILE < workspace AGENTS.md < repo AGENTS.md. The more specific file wins; on conflict, stop and record a question (see Questions). Effective 2026-08-31. ## Promotion tiers (alpha -> beta -> uat -> prod; each slower than the last) - alpha: build + vet + test in the digest-pinned builder (`./dev.sh check`). Nothing deployed. - beta: candidate image on the isolated beta lane (own Postgres, loopback bind; gateway: compose.beta.yaml, 127.0.0.1:4002) + scripted UAT. - uat: the REAL client against the beta lane (crush: `crushgw-uat`; gateway: `./promote.sh uat`). Spend lands in beta accounting only. - prod: the live stack. **HUMAN RELEASE ONLY** (gateway: `./promote.sh release`, typed by human hands). An agent NEVER promotes to prod, NEVER restarts prod for convenience. If prod must bounce, drain-aware: quiet logs, 30s graceful, in-flight turns may retry. - Verified in one tier does not mean verified in the next. Each tier re-proves. ## SDLC doctrine (violating this invalidates the work) 1. Red/green TDD: failing test first, then code, then refactor. 2. Extensive suites: happy path AND sad path AND edge cases for every change. 3. Adversarial review before done: attack your own change (malformed input, null/empty, oversize, race, timeout, auth bypass, resource exhaustion). 4. Two review passes on non-trivial code: friendly (does it work?) and hostile (how does it break?). 5. STRICT sync, same commit: code, tests, docs, AND the Redmine ticket state move together. Never leave any out of sync. Ticket notes/status updated as milestones land (never close/done-ratio without human ruling). 6. Human UAT gates "done". Agent verifies technically (services, wiring, APIs responding); the human accepts. "Technically wired but blank dashboard" is NOT done. Never set done-ratio 100, never close tickets without explicit permission. 7. Verify the TRAIL, never just the answer: prove requests landed (gateway logs, spend rows in the RIGHT Postgres, wire captures). A passing answer from the wrong lane/endpoint is a FAIL. ## Hard rules - ALL builds/tests in Docker via each repo's pinned builder. NEVER run toolchains on the host. Host runs only git, docker, unix utils. - Secrets: ONE place — ~/.creds (0600 env files). NEVER in repos, images, compose files, app databases, logs, or error strings. Configs hold refs (env:/file:), never material. - curl is banned. HTTP from host = python3 urllib. - 100% compose lifecycle: every container is a compose service with container_name; storage in named volumes; no ad-hoc docker run for services, no manual renames. Scoped ops only — NEVER bare `down`. - Image tags: digests or pinned versions, NEVER :latest. - Container naming: `ukrrs--[-beta]` (prod) / same with -beta (beta lane). Compose project mirrors the lane. - DNS names over IP literals (documented exception: the gateway's tailscale service bind). - Every command touching a remote system gets `timeout`: 30s reads / 120s standard / 300s deploys. A hung command is a FAILED command — diagnose, never blind-retry. - Time-box: if stuck twice on the same approach, change approach or record a question. - NEVER push to remotes unless the human asked (repo policies may demand commits; pushes stay human-gated). ## Enforcement (mechanical, belt and suspenders) - Crush PreToolUse ticket-gate (all lanes): inside a git repo, modifying tools are BLOCKED until `echo '#NNN' > .crush/active-ticket` at the repo root. Clear when done: `> .crush/active-ticket`. Scratch outside repos is exempt. - Git hooks are GLOBAL: `git config --global core.hooksPath` points at TSYSGroupAIOS/hooks/global (pre-commit/pre-push). They run universal checks (secret patterns, conflict markers, :latest tags), delegate to the repo's own scripts/check-rules.sh when present, and chain the repo's .git/hooks/ when it exists. In genuine emergencies bypass with --no-verify AND note it in the repo JOURNAL the same day. - New fleet repos come from the go-cli-mcp-template Gitea template (enforcement layer baked in). Adopting an existing repo: copy TSYSGroupAIOS scripts/{check-rules.sh,setup-hooks.sh,pre-commit,pre-push} per ADOPTING.md. - If a hook blocks you, fix the cause. Never disable enforcement to proceed. ## Systems of record - Redmine = ALL work tracking (tickets, schedule, decisions-as-work-items). Reference `[#NNN]` in every commit. NEVER close a ticket without explicit human permission. Ticket-first inside repos (see gate above). - Discourse = long-form docs + durable decisions/rationale. Repo .md files are stubs pointing there (exceptions: AGENTS.md, STATUS.md, questions-v*.md, runbooks that must live beside code). - No per-repo JOURNAL.md (ruling 2026-08-31): work lives in Redmine, decisions in Discourse. Existing repo JOURNALs are read-only history — do not extend. - STATUS.md = agent-owned, git-tracked, SUPER SPARSE: active ticket #s + one-line state each + an Inbox for mid-task interrupts. No narrative, no history — Redmine has that. Point-in-time only. ## Cross-linking (gitea ↔ redmine ↔ discourse — always in lockstep) - Every gitea repo README links its Redmine project AND its Discourse doc/category. - Every Redmine project overview links its gitea repo(s) AND Discourse category. - Every Discourse doc/category links its gitea repo AND Redmine project. - Per work item: ticket ↔ Discourse doc ↔ PR/commit ([#NNN] in subject or body). - Links are created AT BIRTH of any artifact and updated in the same turn the work lands. Anyone landing on ANY of the three must reach the other two in one click. An artifact without its links is not done. ## Working style - ROLLING TABLE HUD (every client — crush, openwebui): ONE markdown table, updated IN PLACE at each significant checkpoint (step done, blocker, pivot). Never append a second table. Max 10 rows. States: done ✅ / doing 🔄 / blocked ⛔ / next ⏳. | ticket | tier | state | item | |---|---|---|---| | #587 | beta | 🔄 | promote pipeline UAT | | #588 | alpha | ⏳ | JSON LSP container | - Sub-agents are subcontractors: scoped ask in, DISTILLED findings out. NEVER read 10+ files sequentially; batch into 2-3 agent calls. Read only the 3-4 files you will edit. - Farm out to deterministic tooling first: LSP, linters, formatters, test runners. Do not reason about what a tool can tell you. - Interruptions mid-task: log to STATUS.md Inbox, do not pivot. - Questions: NEVER harness question/modal tools. Write questions in the repo's git-tracked questions-v(N).md; human answers inline; version up per round. Ask early; do not ruminate. ## Crush wiring (v0.91.2 — learned the hard way) - Provider wiring lives in the `crushrc` NEXT TO crush.json (`provider add ... --extra-header X-Consumer