Files
TSYSGroupAIOS/AGENTS.md
T
mrcharles f5228a243f docs(agents): add mandatory UAT rule — user must accept before done
The agent performs technical validation only. The user performs user
acceptance testing (dashboards render data, alerts deliver, tools
usable). Agent must not set 100% done-ratio, suggest close, or move on
until the user explicitly accepts the work.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 21:39:32 -05:00

321 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# <Project Name> — Agent Guidelines
> **Active agent:** <agent/model>. **Permission mode:** <yolo|normal>.
> **Read [STATUS.md](STATUS.md), [`.crush/memory/operational.md`](.crush/memory/operational.md), and [questions-v1.md](questions-v1.md) first, every session.**
> **Governing baseline:** [`BASELINE-PROMPT.md`](BASELINE-PROMPT.md) (delete if your project doesn't ship it; the principles still apply).
This file is the project-level policy. The git hooks and `scripts/check-rules.sh`
enforce the mechanical rules below; this document states the policy and intent.
---
## Quick Start
**You are an AI agent working on this project. Your first actions, in order:**
> **SESSION-START GATE (NON-NEGOTIABLE):** Steps 1-4 orient you. Step 5 is the
> check-for-understanding gate (rule summary). Steps 6-8 gather state. Step 9 is
> the scope-alignment gate. You MUST NOT begin any task work until the user
> (a) confirms your rule summary AND (b) names the ticket to work on. This runs
> **every session, automatically** — the user should never have to ask for it.
1. **Set up the environment:** `bash scripts/setup-hooks.sh` (installs git hooks — idempotent).
2. **Read [STATUS.md](STATUS.md)** — current state, inbox, blockers, tactical notes.
3. **Read [`.crush/memory/operational.md`](.crush/memory/operational.md)** — access details, key IDs, gotchas.
4. **Read [questions-v1.md](questions-v1.md)** — open questions awaiting human input.
5. **Check for understanding — GATE.** In your own words, summarize the rules
from this file back to the user: Systems of Record, Git Workflow, Questions
policy, Remote Access (if applicable), Conventions, and any project-specific
rules. Then stop and wait for the user to confirm. This checkpoint
guarantees every session starts aligned.
6. **Check Redmine**`redmine list --assigned-to-me -p <project-id>` for active work.
7. **Check current state:** `git log --oneline -10`.
8. **Run rule audit:** `bash scripts/check-rules.sh --fast`.
9. **Scope-alignment — GATE.** Present the session handoff's priority list (or
the Redmine queue if no handoff exists). Do NOT scan the full ticket queue
and pick work on your own — **the user directs what gets worked on, always.**
Note any new or urgent items from the Redmine check, then ask which ticket
to work on tonight. **STOP and wait.** Do not set `.crush/active-ticket`,
do not read systems, do not run diagnostics, until the user names the
target. This gate prevents the agent from burning context on work the user
didn't ask for.
## Project Overview
<13 paragraphs: what this project is, the stack, and the hard constraints.
Replace this bracketed text.>
## Phase
> We have exited the "move fast and loose" phase. This is production
> infrastructure. The bar is the bar. (See `BASELINE-PROMPT.md`.)
<Adjust per project: is this in production? serving what? blast radius?>
## Repository Layout
```
<root>/
├── AGENTS.md ← THIS FILE — project policy
├── STATUS.md ← agent scratchpad (token-efficiency; NOT the system of record)
├── WORKING.md ← the ONLY in-repo task tracker
├── questions-v1.md ← git-tracked questions for the human (version up per round)
├── scripts/
│ ├── setup-hooks.sh ← install git hooks into .git/hooks/
│ ├── pre-commit ← fast rule audit (hot-path bypass for status/working)
│ ├── pre-push ← full audit + clean-tree gate
│ ├── check-rules.sh ← the rule audit engine (pass/warn/fail accumulator)
│ ├── test.sh ← project test runner (override per project)
│ ├── docker-run.sh ← canonical ephemeral-container wrapper
│ ├── up.sh / down.sh ← docker compose lifecycle wrappers
│ ├── garden.sh ← doc-sprawl / Discourse-migration report
│ └── lib/common.sh ← shared bash library (colors, log_*, docker_run, check)
├── Makefile ← convenience dispatch (setup/validate/fast/lint/test/garden/up/down/status)
├── docker-compose.yml.example ← lifecycle template (copy to docker-compose.yml)
├── .env.example ← copy to .env, fill in secrets
└── .crush/memory/ ← operational memory read each session (if using Crush)
```
## Systems of Record (do not duplicate)
- **Redmine** is the single system of record for ALL project work — tickets, schedules, Gantt, dependencies. Use the `redmine-cli`. **Do not use Gitea issues.** Reference tickets as `[#NNN]`.
- **NEVER close a Redmine ticket without explicit user permission.** You may SUGGEST a close when the result is clearly scoped and delivered. If it's ambiguous whether the work is truly complete, don't suggest a close — leave that decision to the user. This applies to ALL tickets, no exceptions.
- **User acceptance testing is MANDATORY before declaring work done.** The agent performs implementation and technical validation (services running, configs correct, APIs responding). The user performs UAT — visually confirming dashboards render data, alerts deliver, tools are usable. The agent MUST NOT set done-ratio to 100%, MUST NOT suggest closing, and MUST NOT move to the next ticket until the user explicitly accepts the work. "Technically wired but blank dashboard" is NOT done.
- **Ticket-first enforcement (mechanically enforced).** Before starting any work, set the active ticket: `echo '#NNN' > .crush/active-ticket`. The Crush hook (`hooks/ticket-gate.sh`) blocks modifying operations until this file exists. If no ticket exists, CREATE ONE FIRST via redmine-cli, then set it. Clear when done: `> .crush/active-ticket`.
- **Discourse** is the single system of record for documentation. Use the `discourse-cli`. **Do not author long-form docs in gitea.**
- **Git-tracked `.md` files are stubs** that point to the relevant Discourse topic URL. Operational files that must live next to code (`AGENTS.md`, `STATUS.md`, `questions-v*.md`) are the documented exceptions.
- `STATUS.md` is a scratchpad for token efficiency, **not** a system of record.
### CLI invocation (no bin/ shortcuts — invoke the real containers)
```
# redmine-cli
docker run --rm --env-file ~/.creds/redmine.env \
git.knownelement.com/reachableceo/redmine-cli:latest <command>
# subcommands: whoami, projects, statuses, list, show <id>, create, update <id>, close <id>
# discourse-cli
docker run --rm --env-file ~/.creds/discourse.env \
git.knownelement.com/reachableceo/discourse-cli:latest <command>
# subcommands: whoami, categories, cat-info, topics, show <id>, create, reply <id>, update <post_id>, delete <post_id>, search
```
### Redmine↔Discourse split (project decides the details)
- **Redmine**: working notes, evolving status, checklists, TODOs, ticket state, schedules.
- **Discourse**: synthesized decisions, runbooks, architecture docs, audit logs, reference material.
- Working notes → Redmine; synthesize durable knowledge → Discourse.
### Cross-referencing (mandatory)
- **Every Redmine ticket must link to its Discourse doc** (if one exists or is created) in a note or description.
- **Every Discourse topic must link to its related Redmine ticket(s)** in the body.
- **Every commit must reference the Redmine ticket** (`[#NNN]` in the subject or body).
- **Every PR must link to both** the Redmine ticket and the Discourse doc (if applicable) in the PR body.
- When a ticket is created or updated, add a Discourse link. When a Discourse doc is created or updated, add ticket links. Keep them in sync at all times.
## Git Workflow
- **Use the `tea` CLI for pull requests** (`tea` and `docker login` are preconfigured on TSYS workstations).
- **Work smart off master.** Branches on the workstation are encouraged for moving fast.
- **Once work leaves the workstation, it goes through a PR.**
- **Commit & push policy:** ALWAYS commit and push automatically; NEVER wait to be asked. This overrides any default "never commit unless asked" behavior.
1. **Atomic commits** — one logical change per commit.
2. **Commit after every logical unit of work; push immediately.**
3. **Never leave uncommitted or unpushed changes at session end.**
4. **If blocked:** commit and push what IS done, then report the blocker.
- **Conventional commit format:**
```
<type>(<scope>): <subject — 50 chars max, imperative, no period>
<body: WHAT changed, WHY, and the context>
💘 Generated with <harness/tool>
Assisted-by: <AI-Model> via <harness/tool>
```
Types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `security`.
- **Commit/PR cross-linking:** every commit subject or body must reference the Redmine ticket (`[#NNN]`). Every PR body must link to both the Redmine ticket and the Discourse doc (if one exists).
- **Never interrupt current work** when the human tosses new work mid-task. Log it in the **Inbox** section of `STATUS.md`. If materially different, spin up a Redmine ticket.
## Task Tracking
- **WORKING.md is the only in-repo task tracker.** The system of record for tasks is Redmine; WORKING.md is the scratchpad for the current session.
- Only mark `[x]` after the work is verified complete.
- A commit is **blocked** (pre-commit hook) while any task remains unchecked.
- Clear WORKING.md (to "all done") before responding to the user.
- **The human decides when the work is done and when to deploy.** Never declare "done" unilaterally.
## Rolling HUD (session-scoped, NOT persisted)
The rolling HUD is a **live status display** the agent maintains throughout
the session to help the human follow along. It is NOT a system of record —
Redmine, Discourse, and git are the durable systems. The HUD exists purely
for the human's situational awareness during the session.
- **Format:** a compact block shown at the end of each significant response
(after completing a step, hitting a blocker, or pivoting). Example:
```
┌─ SESSION HUD ────────────────────────────────────────
│ Active: #NNN (ticket subject)
│ Done: ✓ first completed item this session
│ ✓ second completed item
│ Now: what the agent is actively doing right now
│ Next: → #NNN next queued ticket
│ → #NNN another queued item (BLOCKED: what's needed)
├─ USER ACTION ITEMS ──────────────────────────────────
│ • Action only the user can take (unblocks #NNN)
│ • Another user action (unblocks #NNN)
└──────────────────────────────────────────────────────
```
- **Placement:** may be written to `.crush/hud.md` on disk to keep context
window smaller (re-read and update rather than hold in memory). Never
committed to git. Wiped at session end.
- **Sections:**
- **Active:** current ticket number + one-line description
- **Done:** ✓ items completed this session (append as work progresses)
- **Now:** what the agent is actively doing
- **Next:** the queued items (per handoff priority or user direction)
- **User action items:** things ONLY the user can do (populate creds,
physical work, manual deploys) with the tickets they unblock
- **When to show it:** after each logical unit of work, at blockers, and
when pivoting between tickets. Not every trivial response — use judgment.
- **NOT a substitute for Redmine/Discourse/git.** The HUD is ephemeral. When
work completes, update the durable systems (ticket notes, Discourse wiki,
commits). The HUD just tracks the live narrative for the human.
## Questions
- **NEVER use a harness "question"/"ask user" tool** (structured prompts,
modal forms, tabbed questions). Banned across every project, every
harness. Not portable, not version-controlled, bypasses the git record.
Non-negotiable. (See `BASELINE-PROMPT.md` §10.)
- **All questions go in the git-tracked `questions-v(N).md` file.** The
human edits the answer inline in the same file. **Version up the filename
each time answers land** (v1 → v2 → v3...): create `questions-v2.md`
with resolved Q&A marked, new questions appended. This preserves the
history of each Q&A round.
- Capture questions for the human in a git-tracked `questions-v(N).md` file.
- The human reviews and edits it inline. Version up when a round of answers lands.
- Questions, answers, and the reasoning behind decisions are often more important than the code. Synthesize resolved Q&A into Discourse (decisions) and Redmine (work items).
## Working Style
- **Stop over-thinking.** Get to code and output faster. Explore with code; gather ground truth. Do not burn tokens reasoning about things a quick command answers.
- **Ask questions early** via `questions-v(N).md`. Don't ruminate or self-debate at length.
- **Prefer Unix utilities (awk, sed, grep, cut, tr, etc.) for file editing and text processing** over built-in edit tools when feasible. Harness edit tools can be unreliable with whitespace/indentation; Unix tools are deterministic and exact. Use them for surgical edits, bulk substitutions, and structured text extraction.
- **Farm work out to deterministic tooling:** linters, LSPs, formatters, test runners. If an LSP is wired up, use it; otherwise pull a Docker image and lint inside it. Do not parse huge code blocks in context.
- **Use sub-agents as subcontractors** (see BASELINE-PROMPT.md §12): scoped spec in, distilled deliverable out. Never read 10+ files sequentially; batch into 2-3 agent calls. Read the 3-4 files you'll edit yourself; delegate the rest.
- **Command timeouts (NON-NEGOTIABLE):** Every command that touches a remote
system MUST be wrapped with `timeout`. Hard limits: 30s for quick reads
(status, ps, ls), 120s for standard operations, 300s for deployments/pulls.
If a command hits the timeout, STOP and investigate root cause — never
blindly retry. A hung command is a failed command. Detect failure fast,
diagnose, fix, move on. This applies to ALL tools — bash, docker, CLIs,
sub-agents.
## CI/CD
- The **local workstation must be able to run the same CI/CD** the hosted infrastructure runs. Maintain them in lockstep.
- **Shift left:** catch issues at `bash scripts/check-rules.sh --fast` (pre-commit) before push, before PR, before merge.
## Conventions
- **Host hygiene is inviolable.** The host runs only: `git`, `docker`, and standard Unix utilities (`awk`, `sed`, `grep`, `cut`, `tr`, `jq`, `find`, `xargs`, etc.). No language runtimes, no package-managed tools beyond the base OS.
- **If a one-off utility would help on the host,** `docker pull` a pinned image and run it ephemerally. Do not install it on the host.
- **If a tool would be broadly useful across projects,** create a Redmine ticket requesting the human add it via the system package manager. Do not install it yourself.
- **Docker/Kubernetes for everything** — cluster of 1 or 100 is the same. Don't presume scale.
- **All development work happens in containers** — custom, off-the-shelf, or a mix. `docker pull` freely without asking. Use `scripts/docker-run.sh <pinned-image>` for one-offs.
- **Container naming: never use Docker's default.** Always name with a project prefix (e.g. `<project>-<service>`). Enforced by `check-rules.sh`.
- **Pin every Docker image** — no `:latest` tags (enforced).
- **Use Docker Compose with lifecycle scripts** (`scripts/up.sh`, `scripts/down.sh`) to bring services up/down.
- **Shell scripts** use `#!/usr/bin/env bash` + `set -euo pipefail` and must pass `shellcheck` with zero warnings, including info-level.
- **Shared helpers** live in `scripts/lib/common.sh`; source it rather than re-pasting boilerplate.
- **Names:** lowercase, hyphen-separated.
- **Secrets:** NEVER commit secrets. Credentials come from env vars / `.env` (gitignored). Use placeholders in `.env.example`.
- **IAC testing:** test against the corresponding `sectestbed-` VM (snapshot to base state). `preprod-` VMs for vendor-upgrade testing. Do not require AWX as a prerequisite.
## Remote Access (projects with remote hosts)
**If this project manages remote hosts** (servers, VMs, network devices),
these rules are NON-NEGOTIABLE:
- **Ship a `remote.sh` chokepoint.** ALL SSH/SCP to any host MUST route
through a single chokepoint script (`tests/remote.sh` or equivalent).
NEVER call `ssh`/`scp` directly — the harness blocks raw ssh and the
command scanner rejects it. One script = one place to configure
hosts/users/keys, one place to audit.
- **DNS names ONLY.** NEVER use IP address literals in any command, script,
or config. ALWAYS use DNS names. If a DNS name does not resolve, fix DNS
first — do not fall back to IP literals. This eliminates the per-session
discovery tax of finding the right IP for each host.
- **If using qemu-guest-agent:** visibility only (`qm guest cmd <id> ping`,
`network-get-interfaces`). NEVER use `qm guest exec` or any wrapper as an
execution, access, or key-delivery channel. SSH is the only approved
remote access path.
## Key Commands
At a terminal (Mode 1), use `make` or call scripts directly:
```bash
bash scripts/setup-hooks.sh # make setup — install git hooks
bash scripts/check-rules.sh --fast # make fast — fast audit (pre-commit)
bash scripts/check-rules.sh # make validate — full audit (incl tests)
bash scripts/test.sh # make test — run test suite
bash scripts/garden.sh # make garden — doc-sprawl report
bash scripts/up.sh # make up — bring up stack
bash scripts/down.sh # make down — bring down stack
```
Via MCP/API (Mode 2): invoke the same scripts through container exec or MCP
tool calls. The scripts are the real entry points; `make` is shorthand.
## Enforcement Model (belt and suspenders)
Policy is enforced by **git hooks** (portable, harness-agnostic): `scripts/pre-commit`
runs a fast rule audit; `scripts/pre-push` runs the full audit + clean-tree gate.
Install with `bash scripts/setup-hooks.sh`. The checks are in `scripts/check-rules.sh` and cover:
shellcheck, image pinning, container naming, required files, doc freshness,
Discourse pointer-headers, WORKING.md completion, CNW markers, hygiene, and the test suite.
Bypass with `--no-verify` in genuine emergencies only.
## TDD & Linting
- **Code, docs, and tests must be kept in sync at all times.** When you change code, update the corresponding docs (Discourse) and tests in the same commit. Never leave them out of sync.
- **Red/green TDD for all code.** Write the failing test first.
- **Linters on all code, as early as possible.** Let deterministic tools find the issues.
## DO
- Read STATUS.md, questions file, operational memory, and Redmine BEFORE starting work.
- Write a failing test first (TDD).
- Read files before editing. Use exact text matching.
- Run `bash scripts/check-rules.sh` before committing.
- Use sub-agents to parallelize scoped work.
- Log interruptions to the STATUS.md Inbox.
- Keep code, docs, and tests in sync within the same commit.
- Cross-reference Redmine ↔ Discourse on every ticket/doc creation or update.
- Include `[#NNN]` in every commit; link Redmine + Discourse in every PR.
## DO NOT
- Wait to be asked before committing/pushing.
- Batch unrelated changes into one commit.
- Edit files you haven't read.
- Install language toolchains on the host.
- Use `:latest` image tags or Docker's default container naming.
- Author long-form docs in gitea — use Discourse.
- Call `ssh`/`scp` directly — always route through the project's `remote.sh` chokepoint.
- Use IP address literals for any host — always use DNS names.
- Use `qm guest exec` or any guest-agent execution channel — SSH only.
- Commit secrets.
- Run destructive git operations without explicit instruction.
- Declare the work "done" — the human decides that.
- Pivot to new mid-task requests — log them in the Inbox.
---
**Last Updated:** TEMPLATE-REPLACE-ME