This file provides essential guidance to Claude Code (and other agents) when working with this repository.
f0_sectools is an open-source library of tools and skills for AI agents that connect to security platforms — SIEM/XDR, EDR, identity, and threat-intelligence systems — to understand security posture, assess risk, and help take the right course of action for a SOC analyst, security engineer, threat hunter, or CISO.
It is the defensive / operational counterpart to f0_library (offensive EDR detection testing). Both are part of the F0RT1KA brand and the ProjectAchilles ecosystem, licensed under Apache 2.0.
The entire repo is designed so organizations can run security-operations agents on their own infrastructure, with small open-weight models — GPT-OSS (20b/120b), Gemma 4, Qwen3 — served locally via vLLM or llama.cpp. No telemetry, no sensitive security data leaving the host, no dependency on a frontier cloud API.
This single constraint drives almost every design rule below. Small local models are now genuinely good at tool calling, but their reliability degrades sharply with complex schemas, too many tools, aggressive quantization, and oversized payloads. Our job is to build tools these models can actually drive, reliably, on real security data.
- MCP servers (
servers/) — one thin Model Context Protocol server per platform, exposing read tools (and gated write actions). Runtime-agnostic: they target the OpenAI-compatible API surface, so they work behind vLLM, llama.cpp, or any compliant front-end. - Skills (
skills/) — higher-level playbooks that orchestrate the servers (e.g. "triage a Defender incident", "build a posture summary for the CISO"). They follow the agentskills.io open standard (SKILL.md) — originally Anthropic's Agent Skills format, now adopted by Hermes, Claude Code, Goose, and others. One portable set, no runtime-specific forks. - Personas — four role lenses (CISO, threat hunter, detection engineer, security engineer) that shape output. Delivered as Hermes
agent.personalities(switched with/personality, not Profiles) and mirrored as switchable modes in the portable system prompt. - Runtimes — primary target is Hermes Agent (skills-aware, native MCP, OpenAI-compatible backend; see
integrations/hermes/). The same skills run under Claude Code and other agentskills.io clients. For non-skill chat UIs (LM Studio, Open WebUI) a portable system prompt inprompts/carries the same guidance. See Skills, Personas & Runtimes.
- Read-only by default. Every tool that queries a platform is read-only. Any tool that changes state on a live platform (isolate host, disable user, quarantine file, close incident) is a gated write action — see Gated Write Actions. It MUST require an explicit config flag AND per-action human confirmation, in one of two modes: (a) forge-resistant — a single-use confirmation token or a watcher approval delivered out-of-band on a channel the model cannot read; this is the default and the only permitted mode for destructive or irreversible actions. (b) chat-confirm — an opt-in, per-platform mode (off by default) where the operator's in-chat "approved" is the confirmation; it is convenient for supervised, reversible actions but is not forge-resistant (a misaligned model could fabricate it), so it is never used for destructive actions.
- Secrets never leave the host and never reach the model. Credentials are loaded from per-platform
.envfiles. They are never logged, never included in tool output, never passed into a prompt or model context, never sent off-box. - Redact before returning. All tool output passes through the core redaction layer before it is returned to the agent. Strip API keys, tokens, raw PII, and secrets from every payload — including error messages and stack traces.
- Every tool returns the structured findings schema. No tool returns ad-hoc text. Output is normalized JSON (see The Findings Schema) so agents — and small models especially — can parse and chain results predictably.
- Tools must be small-model-safe. Flat argument schemas, short enums, few tools per server, no deeply nested objects, bounded/paginated output. See Designing Tools for Small Models. This is the repo's reason to exist — do not regress it for convenience.
- All safety logic lives in
core/, never in a server. Redaction, secret handling, the findings schema, and the gated-action machinery are implemented once, in the shared core, and imported by every server. A server must not re-implement or bypass them. - Per-platform credential isolation. Each platform has its own
.env.<platform>. No cross-platform credential bleed; a server only loads its own platform's secrets. - Audit every write action. Gated actions are logged (who/what/when/which target/confirmation token) to a local audit trail — never to an external service.
- Skills are one portable set, not per-runtime forks. All skills follow the agentskills.io
SKILL.mdstandard and live once inskills/. Runtime-specific wiring (Hermes config/personas, the LM Studio system prompt) lives inintegrations/andprompts/and must not duplicate skill content — same DRY discipline as rule 6.
This is the core differentiator of f0_sectools. A tool can be functionally perfect and still fail because a small local model cannot reliably call it. Design every tool against these rules.
Assume the model is GPT-OSS-20b / Gemma 4 / Qwen3 at 8-bit quant, served locally. Treat that as the floor, not the ceiling.
- Keep argument schemas flat. Top-level scalar parameters (
host_id: str,severity_min: int). Avoid nested objects and arrays-of-objects as inputs. - Use short, closed enums.
severity: "low" | "medium" | "high" | "critical"— not a free-form string, and not a 40-value enum the model picks wrong from. - Keep few tools per server (target ≤ ~8). Tool-selection accuracy drops as the registered tool count grows. Split a sprawling platform across focused servers rather than registering 20 tools.
- Name parameters descriptively.
alert_idnotid,time_window_hoursnott. The name is the model's primary cue. - Bound and paginate every output. Security APIs return enormous result sets. Default to a small page size, return a
next_cursor, and summarize counts. An unbounded dump blows the context window and silently degrades tool accuracy. - Make reads idempotent and deterministic. Same args → same shape, every time.
- Write tool descriptions for a model, not a human. One clear sentence on when to use it and what it returns.
- ❌ Nested/object-valued arguments or polymorphic params.
- ❌ Long free-text arguments the model must construct (e.g. raw KQL/SPL) without a guided, validated helper.
- ❌ Registering many tools "just in case."
- ❌ Returning raw, unbounded platform JSON.
- ❌ Relying on the model to remember multi-step state across calls — encode it in args or in a skill.
Documented failure modes for this model class: degraded tool accuracy when context exceeds VRAM (silent CPU fallback), regressions under 4-bit quantization, mis-selection among many tools, and mangled nested arguments. The small-model eval harness exists to measure callability so these defects fail CI instead of surfacing in production.
Shared core library + thin per-platform servers. All cross-cutting and safety-critical logic lives in core/; each server is a thin adapter that knows only its platform's API and tool definitions.
f0_sectools/
core/ # shared package — imported by every server
schema/ # the findings schema + validators
redaction/ # secret/PII stripping (applied to ALL output)
auth/ # per-platform .env loading, token refresh
paging/ # pagination, truncation, rate-limiting
smallmodel/ # tool helpers: flat-arg builders, enum guards, arg validation
gating/ # gated write-action machinery + audit log
renderers/ # persona renderers (analyst/engineer/ciso/hunter/detection-engineer)
reports/ # persona posture reports: builder, emit, pdf, theme, i18n
servers/ # one thin MCP server per platform
defender-mcp/ # built + live-validated
entra-mcp/ # built + live-validated
limacharlie-mcp/ # built + live-validated
projectachilles-mcp/ # built + live-validated
projectachilles-actions-mcp/ # built + live-validated — gated writes
intune-mcp/ # built + live-validated
tenable-mcp/ # built + live-validated
purview-mcp/ # built + live-validated (data risk: DLP, insider risk, labels, audit)
# planned: wazuh, elastic, splunk, sentinel, crowdstrike, sentinelone,
# sophos, misp, thehive, opencti (see Platform Integrations)
skills/ # portable agentskills.io playbooks (SKILL.md) — load in any skills-aware runtime
defender/ entra/ limacharlie/ projectachilles/ intune/ tenable/ purview/
cross-platform/ # multi-server correlation: incident triage, offensive<->defensive loop
reports/ # generate-report
# (full catalog: docs/reference/skills.md — generated)
integrations/ # runtime-specific wiring (NO skill content — see rule 9)
hermes/ # config.example.yaml (manual-merge) + distribution/ (installable profile: distribution.yaml + config.yaml + SOUL.md)
pi/ # mcp.json + AGENTS.md + persona prompt templates
opencode/ # README only — the wiring is in-repo: /opencode.json + /.opencode/{skills,agents}
prompts/ # portable system prompts for non-skill UIs (LM Studio, Open WebUI)
evals/ # small-model tool-calling eval harness + task sets
examples/ # sample findings (CI-validated), transcripts, persona renders
docs/ # hub (README) + explanation/ + reference/ (generated) + user-guide/
The rule: a server defines its tools and calls its platform's API. Everything else — auth, redaction, schema normalization, pagination, gating, rendering — it gets from core/. This keeps the safety guarantees enforceable in one auditable place and prevents drift across a dozen integrations.
We deliberately start with a single shared-core layout (not independently-published packages). If we later decide to ship servers individually (
pip install f0-sectools-wazuh), we graduate to a monorepo-packages layout then — not before (YAGNI).
Every tool returns a normalized finding (or a list of them). This is the source of truth; human-readable views are rendered from it. The authoritative reference is docs/explanation/findings-schema.md (full fields, enums, lifecycle, error-as-finding builders) and the code is core/f0_sectools_core/schema/findings.py — do not restate the field list here or anywhere else; an earlier inline copy of the schema in this file drifted from the code (wrong enums) and misled agents.
Shape at a glance: schema_version · source · finding_type (8-value enum incl. posture for graceful degradations and action for gated-write intents/results) · severity (info→critical) · title · optional entity {kind, id, name} · flat evidence [{key, value}] · optional recommended_action {summary, gated_action, confidence} · references · observed_at. Sample findings for every server (schema-validated in CI): examples/findings/.
core/renderers/ renders the same finding per audience (SOC analyst, security engineer, CISO, threat hunter, detection engineer) — deterministic, model-free, presentation only, never a different data contract. Reference + real rendered examples: findings-schema.md § renderers and examples/personas/.
Two persona layers, don't confuse them.
core/renderers/(above) shapes how a finding's text is presented. The agent personas in Skills, Personas & Runtimes shape the agent's behaviour — which skills/tools it favours and how it frames a whole response. They compose; the renderer is optional polish, the agent persona is the primary mechanism today.
How a local model actually drives these tools. The mechanism differs by runtime, but the content is authored once.
Operator-facing instructions live in the User Guide (
docs/user-guide/) — per-runtime setup, workflows, troubleshooting. This section is the builder's view; keep the two in sync when adding a runtime, skill, or persona, and update the User Guide's support matrix.
Skills live in skills/ as agentskills.io SKILL.md packages (the open standard, originally Anthropic's, now adopted by Hermes, Claude Code, Goose, OpenHands, Cursor, …). A skill is a directory with a SKILL.md (YAML frontmatter: name, description ≤60 chars, version, optional metadata.hermes) plus ## When to Use / Procedure / Pitfalls / Verification, and optional references/. Loaded via progressive disclosure. The same files work in every skills-aware runtime — never fork them per runtime (Critical Rule 9). Each skill refers to tools by base name (list_incidents); runtimes prefix differently (Hermes mcp_f0-defender_list_incidents, Claude Code mcp__f0-defender__list_incidents).
The full skill list lives in the generated catalog docs/reference/skills.md (27 skills across 9 platform categories; regenerate with uv run python scripts/gen_docs.py — do not hand-list skills here or anywhere else). Default focuses: LimaCharlie → endpoint investigation, Intune → device-compliance review, Tenable → exposure-posture review, Purview → data-risk review; the cross-platform correlation playbooks favour a capable local model. A test (skills/test_skills_valid.py) enforces valid frontmatter and the ≤60-char description limit on every SKILL.md.
CISO, threat hunter, detection engineer, security engineer — each a behavioural lens (focus + output style + which skills/tools to favour). Shared identity and the read-only / never-fabricate principles live in one place; each persona only adds its lens. Delivered as Hermes agent.personalities (switch with /personality <name>) and mirrored as switchable modes in the portable prompt.
- Hermes Agent (primary) — skills-aware, native MCP, OpenAI-compatible backend.
integrations/hermes/holdsconfig.example.yaml(the manual-merge template — wiresmcp_servers, pointsskills.external_dirsat this repo'sskills/in place, defines the four personalities) anddistribution/— a git-installable profile distribution (distribution.yamlmanifest +config.yaml+SOUL.md;hermes profile install ./integrations/hermes/distribution). NB: Hermes reads MCP servers fromconfig.yaml'smcp_servers(a distributionmcp.jsonis not auto-loaded by the CLI), and the gated-write server ships disabled-by-default. See its README +docs/user-guide/runtimes/hermes.md. - Claude Code / other agentskills.io clients — the same
skills/load unmodified. - pi (pi.dev) — minimal agentskills.io terminal harness; the same
skills/load unmodified. No native MCP — bridge our servers with thepi-mcp-extension.integrations/pi/holdsmcp.json,AGENTS.md(base identity), and the four persona prompt templates. Seedocs/user-guide/runtimes/pi.md. - opencode (opencode.ai, ≥1.18) — terminal agent with native MCP and native SKILL.md skills. Wiring ships in-repo: root
opencode.json(8 servers, relative commands,f0-pa-actionsdisabled),.opencode/skills/(27 committed symlinks intoskills/— no forks),.opencode/agents/(4 persona agents). Run opencode from the checkout and it all auto-loads. Seeintegrations/opencode/README.md+docs/user-guide/runtimes/opencode.md. - Non-skill chat UIs (LM Studio, Open WebUI) — no skill system; paste
prompts/f0-sectools-system-prompt.md(persona-switchable) as the system prompt. Seedocs/running-with-local-models.md.
Rule of thumb: skill content and persona definitions are authored once; integrations/ and prompts/ only carry runtime wiring, never copies of skill logic.
Any tool that changes state on a live platform is read-only-by-default and gated. The operator-facing trust story (threat model, state machine, verification steps) is docs/explanation/security-model.md — keep it in sync with this section. The pattern, implemented once in core/gating/:
- Disabled unless enabled. The action is unavailable unless the operator sets the platform's write flag (e.g.
DEFENDER_ALLOW_WRITE=truein.env.defender). - Dry-run / intent first. When invoked, the tool returns the intended action as a finding (
finding_type: "action") describing exactly what it will do and to which target — it does not execute yet. - Human confirmation required. Three surfaces, implemented in
core/gating/. The first two are forge-resistant (single-use, target-bound, TTL'd, and the confirmation never round-trips through model context) and are the default and the only modes permitted for destructive or irreversible actions; the third is opt-in and model-forgeable — see the caveat below.- Watcher (default): the intent registers a pending request; the
operator approves it in
python scripts/confirm_action.py --watch(one keypress), and the agent repeats the identical tool call — the gate consumes the stored approval. No token ever enters model context. - Token (headless/scripted):
confirm_action.py <action> "<target>"prints a single-use token passed asconfirmation_token(used by e.g. the live-smoke--executeflows). - Chat-confirm (opt-in, off by default): enabled per-platform via
<PLATFORM>_CONFIRM_MODE=chat(today:PROJECTACHILLES_CONFIRM_MODE, theprojectachilles-actionsserver only). The operator simply replies "approved" in the chat; the agent re-calls the same tool passingconfirmation_tokenequal to theconfirmation_targetshown in the intent finding. Execution is audited withmethod=chat-confirm, same as any other gated action. Gating state lives under$F0_GATING_DIR(default~/.f0sectools/gating/), shared by servers and the CLI regardless of working directory. No confirmation → no execution.
- Watcher (default): the intent registers a pending request; the
operator approves it in
- Execute + audit. On a valid token or consumed approval, the action runs and the result, target, actor, and token/method are written to the local audit trail.
A small local model must never be able to isolate a host or disable an account on its own. The flag + human confirmation (watcher approval or token) is the hard stop.
Chat-confirm's honest caveat: unlike the watcher and token surfaces, chat-confirm's "confirmation" is text the model itself can see and echo back — there is no out-of-band channel the model is locked out of. A misaligned or jailbroken model could, in principle, fabricate the operator's "approved" and confirm its own action. It exists only as a low-friction convenience for supervised, reversible operations (e.g. running a validation test) where the operator is watching every turn — it is off by default, must stay opt-in per platform, and must never be wired to a destructive or irreversible action (isolate host, disable user, quarantine file, close incident, delete anything). If in doubt, use the watcher or token surface instead. It is also not single-use or time-limited: confirmation_token == target authorizes every call while the write flag is on, including a silent re-execute if the model retries a failed run with the same arguments — the operator must give a fresh "approved" before each re-call, and the model must never reuse the echo to retry a failed execution. This is by design (making it single-use would reintroduce a token and defeat the point of chat mode), so chat-confirm suits supervised sessions only, never unattended operation.
Note: the gate's guarantee holds only when confirm_action.py runs in a terminal the model cannot drive — in runtimes where the model has shell access, treat the approval CLI (especially --approve) as operator-only and keep write flags off.
Targets (build incrementally — the six built servers below are the reference implementations):
| Platform | Category | Auth | Read | Gated write (examples) |
|---|---|---|---|---|
| Wazuh | SIEM/XDR (OSS) | API user/token | alerts, agents, rules, posture | — |
| Elastic / OpenSearch | SIEM (OSS) | API key | detections, queries | — |
| Splunk | SIEM | token | searches, notables | — |
| Microsoft Sentinel | SIEM | Entra app | incidents, analytics | close incident |
| Microsoft Defender | EDR | Entra app | incidents, devices, guided hunt | isolate host |
| CrowdStrike | EDR | OAuth2 | detections, hosts | contain host |
| SentinelOne | EDR | API token | threats, agents | quarantine, isolate |
| Sophos | EDR | API cred | alerts, endpoints | isolate |
| Entra ID / Azure | Identity | Entra app | sign-ins, risky users, roles | disable user |
| MISP | Threat intel (OSS) | API key | events, IOCs, enrichment | — |
| TheHive / Cortex | IR (OSS) | API key | cases, observables, analyzers | create/close case |
| OpenCTI | Threat intel (OSS) | API token | entities, relationships | — |
| LimaCharlie | SecOps/EDR/XDR | OID + API key (SDK) | sensors, detections, D&R rules, LCQL telemetry | isolate sensor (future) |
| ProjectAchilles | Security validation (F0RT1KA) | pa_ API key (Bearer) |
defense score, test results, weak techniques, agents | run/schedule/pause/cancel test (actions server) |
| Intune | Identity/Endpoint mgmt | Entra app | devices, compliance, policies | — |
| Tenable | Vulnerability Management | API key (access+secret) | vulnerabilities, assets, scans | — |
| Microsoft Purview | Data security/compliance | Entra app | DLP alerts, insider-risk alerts, sensitivity labels, unified-audit search | — |
Each integration follows .env.<platform> and the thin-server pattern. Read tools first; gated writes only where operationally valuable and clearly worth the risk.
Implemented & live-validated: defender-mcp, entra-mcp, limacharlie-mcp (the last uses the official limacharlie Python SDK and closes the offensive↔defensive loop with f0_library's D&R rules). Implemented & live-validated: projectachilles-mcp (read-only over the PA REST API with a pa_ Bearer key — defense score, test results, weak techniques, agents). The PA API lives on the agent subdomain (e.g. https://<org>.agent.projectachilles.io). Implemented & live-validated: tenable-mcp (read-only over the Tenable Vulnerability Management Workbenches API with X-ApiKeys access/secret keys — vulnerability summary, top vulnerabilities, assets, per-asset vulnerabilities, plugin detail, scans, plugin affected-hosts). The official Go lc-mcp-server is a different tool (278 tools, write-capable, optional cloud LLM) — referenced in the user guide as the frontier-model alternative, intentionally not incorporated (it's incompatible with the small-model-safe, local-only, read-only-gated thesis). projectachilles-actions-mcp is built (7 tools: 4 gated writes — run_test, schedule_test, set_schedule_status, cancel_tasks (single task_id or a bulk status/search filter, count-bound confirmation, 200 cap) — + 3 reads — list_schedules, get_task_status, list_tasks), the second consumer of core/gating/ after Defender, live-validated on a real tenant with a read-write-scope pa_ key (single-host and tag/fleet runs, count-bound bulk cancel).
The canonical 12-step recipe lives in CONTRIBUTING.md — config → scaffold → client → errors → tools → server → evals → smoke script → live-test → skills → docs/wiring → verify. Follow it there; do not duplicate it. Agent-specific additions on top of the recipe:
- TDD each code step — write the contract test (fake client) before the implementation.
- Step 9 (live-test) needs network/sandbox enabled and explicit user confirmation (live platform rule) — this step always finds 1–3 field-name mismatches; fix forward.
- Step 11's generated docs (
uv run python scripts/gen_docs.py) and the integration templates (integrations/test_integrations_valid.py) are both drift-guarded — CI fails if either is stale. Also update the Platform Integrations table + Architecture tree here and wire Hermes personas if relevant. - The
gen_docsdrift guard fires on ANY change, not just new platforms. Any edit to a tool, a tool docstring/signature, or a skill makesdocs/reference/stale — rerunuv run python scripts/gen_docs.pyand commit the result before pushing, or CI'stest_gen_docs.pyfails. Same reflex as theintegrations/templates: change code → regenerate → commit. - Step 12 ships with a conventional commit + the Co-Authored-By/session trailers, and push only on explicit instruction.
Auth models already handled (none required a core/ change): Microsoft Graph OAuth client-credentials, a synchronous vendor SDK (LimaCharlie), and a static Bearer REST key (ProjectAchilles). See the Quick Reference table for the one-liners.
- Per-platform
.env..env.wazuh,.env.defender,.env.entra, … Each server loads only its own. All.env*files are gitignored. - Nothing leaves the host. No telemetry, no analytics, no external calls except to the operator's own configured security platforms.
- Secrets never reach the model. Credentials live in
core/auth/; they are used to make API calls and are never placed in tool output, prompts, or model context. - Redaction is mandatory and centralized. Every return path goes through
core/redaction/, including error/exception paths.
Two layers, both expected. Contract tests are mandatory from day one; the small-model eval is built alongside the first server so schema habits are validated before they harden.
Run each server against mocked platform APIs. Verify:
- Tools return correctly-shaped findings (schema validation).
- Redaction strips secrets/PII from output and error paths.
- Gated writes refuse without the flag and without a valid confirmation token.
- Pagination/truncation behave under large mocked result sets.
Deterministic, fast, no model or live platform required.
A harness in evals/ that points a real local model (GPT-OSS-20b, Gemma 4, Qwen3) — served via vLLM or llama.cpp's OpenAI-compatible endpoint — at a server's tools and measures callability, not just code correctness:
- Tool-selection accuracy — given a natural-language task, does the model pick the right tool?
- Argument-filling success rate — does it populate args correctly, N runs each?
- Degradation signals — flag tools that score poorly (too many tools, enum too large, nested args) as design defects to fix, reported as success rates, not pass/fail.
Task sets live as YAML in evals/. The eval can run locally against a GPU box or as a scheduled (e.g. nightly) job rather than on every commit.
If a tool passes Layer A but fails Layer B, the tool's design is wrong — simplify the schema, don't lower the bar.
Every push/PR runs (.github/workflows/):
- ci —
uv run pytest(offline contract + harness-logic tests) +uv run ruff check .+uv run mypy .(strict) — all hard gates. mypy is scoped to shipped source (core/+ each server's package); tests,evals/,scripts/, andskills/are excluded (strict-typing mocks/fixtures/tooling is high-noise, low-value). - secret-scan (gitleaks) and semgrep (SAST, gate on
p/python;p/security-auditadvisory) — hard gates. - deps (pip-audit), links (lychee), codeql (dormant until the repo is public) — advisory, not required checks.
Live-model evals and live-platform smoke scripts are never run in CI (no creds/GPU) — they stay local. Mark only ci, secret-scan, and semgrep as required status checks in branch protection.
Claude PR review is provided by the Claude Code GitHub App (installed on the repo), which ships two workflows: claude-code-review.yml (auto-reviews every PR via the code-review plugin — it reads this CLAUDE.md, so the Critical Rules stay in scope) and claude.yml (responds to @claude mentions in issues/PR comments). Both authenticate with the CLAUDE_CODE_OAUTH_TOKEN secret; they post advisory comments and do not block merge. The earlier self-hosted claude-review.yml action (var-gated on ENABLE_CLAUDE_REVIEW + ANTHROPIC_API_KEY) was removed in favour of the app — the ENABLE_CLAUDE_REVIEW variable and the now-unused ANTHROPIC_API_KEY secret can be deleted from repo settings.
This repo mirrors f0_library's house workflow.
Claude Code operates in autonomous mode for repository/code work — proceed without asking for standard development operations:
- Create/edit/delete files and directories in the project
- Git add, commit, branch, checkout (push is gated — see below)
- Run builds, tests, linters, dependency installs (
uv,pip) - Fix build/import/test errors
- Any
git pushto remote — never push autonomously. Commit locally, surface the pending commit hash, and wait for the user to say "push" (or push themselves). - Destructive git operations —
git push --force,git reset --hardto remote. - Any tool call against a LIVE security platform — calling a real Wazuh/Defender/Entra/etc. endpoint (especially any gated write) requires explicit confirmation; do not hit production platforms autonomously.
- Credential /
.envoperations — never create, print, or exfiltrate secrets. - Ambiguous requirements — when intent is unclear.
Conventional commits; do not push autonomously. Stage specific files rather than git add -A to avoid pulling in .env*, secrets, or unrelated work.
git add <specific-files> && git commit -m "feat(wazuh): add read-only alert query tool"
# After commit: report the commit hash, wait for explicit push instruction.Note: this directory is not yet a git repository. Run
git initbefore the first commit; add a LICENSE (Apache 2.0), NOTICE, README, and.gitignore(must ignore.env*,.venv/, caches) consistent withf0_library.
- Language: Python (3.11+).
- Protocol: Model Context Protocol (MCP) Python SDK for servers.
- Model serving (targets): vLLM and llama.cpp via their OpenAI-compatible endpoints; stay runtime-agnostic — do not special-case a runtime.
- Packaging/env:
uv(orhatch) workspace; per-server entry points importingcore/. - Testing:
pytestfor contract tests; theevals/harness for small-model callability. - Lint/format: ruff.
| If you're… | Then… |
|---|---|
| Adding a platform | Create servers/<platform>-mcp/, import core/, define ≤~8 flat read tools first |
| Adding a write action | Route through core/gating/; require flag + confirmation token; audit it |
| Returning data | Emit the findings schema; let core/redaction/ and core/renderers/ do the rest |
| Designing a tool arg | Flat scalar, descriptive name, short closed enum — never nested/objects |
| Tempted to dump platform JSON | Paginate, bound, summarize — protect the context window |
| Writing a skill | One SKILL.md in skills/ (agentskills.io); refer to tools by base name; never fork per runtime |
| Adding a persona/runtime wiring | Hermes → integrations/hermes/; non-skill UI → prompts/. Wiring only, no skill content |
| About to push | Don't. Commit, surface the hash, wait for the user |