> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Walk the Policy Auditor Agent and System Prompt

> Explains a Policy Auditor agent demo that separates user task prompts from system-level tagging rules, injects YAML policies, builds LLM tools, and prevents hallucinations during Kubernetes label audits

Welcome back. In this lesson we'll inspect how the Policy Auditor agent is prompted and how tagging rules are supplied to the agent. We'll walk through two lightweight Python modules:

* The first prepares the user-facing audit prompt and loads tagging rules from disk.
* The second builds the LLM, converts MCP servers into LangChain-style tools, and injects a strict system-level message containing the tagging rules.

We will:

* Review the two source files and their responsibilities.
* Examine how the user prompt and system message are separated and why that reduces hallucinations.
* Highlight the exact places where tagging rules are enforced.

Relevant links:

* MCP: [https://learn.kodekloud.com/user/courses/mcp-for-beginners](https://learn.kodekloud.com/user/courses/mcp-for-beginners)
* LangChain: [https://learn.kodekloud.com/user/courses/langchain](https://learn.kodekloud.com/user/courses/langchain)

Structure

* `structure_auditor.py` — prepares the user prompt, loads tagging rules, then calls `run_agent`.
* `mcp_client.py` — builds the LLM, converts MCP servers into LangChain-style tools, composes messages (including the system message), and runs the agent.

structure\_auditor.py
This module defines a concise user prompt (what the agent should accomplish), loads the tagging rules YAML from disk, and calls `run_agent()` with the prompt and the rules. Keeping the user prompt focused on the audit task reduces complexity and lets the system message supply non-negotiable policy.

```python theme={null}
# structure_auditor.py
import asyncio
from pathlib import Path
from mcp_client import run_agent

TAGGING_RULES_PATH = Path(__file__).parents[1] / "config" / "tagging-rules.yaml"

PROMPT = """
You are auditing Kubernetes resource labels for FinOps governance through MCP tools.

Use the available Kubernetes MCP tools to:
1. List all namespaces.
2. For each application namespace (skip only namespaces listed in excluded_namespaces;
   also skip kube-* namespaces), fetch deployments, configmaps, and persistent volume claims.
3. Report each resource's name, namespace, kind, and labels exactly as returned by the tools.
4. Evaluate each resource against the tagging rules provided separately.
5. Flag resources that violate required tags or label mappings.
6. Summarize the full label audit in plain English, grouped by namespace. Cover every
   audited namespace and resource — do not truncate mid-report.

Evaluation rules (follow strictly; do not invent policy):
- A required tag is present if ANY key listed under that tag in label_mappings appears
  on the resource (for example, `app` satisfies `application`).
- Do not call a mapped key non-standard, legacy, or invalid when it is in label_mappings.
- Use only required_tags, label_mappings, cost_centers, environments, tiers,
  compliance_levels, resource_types, and excluded_namespaces from the rules.
- Do not invent exclusions, bonus labels, recommended tags, or extra commentary
  beyond what the rules define.

Use only data returned by the tools. Do not invent labels or resources.
"""

def load_tagging_rules() -> str:
    if not TAGGING_RULES_PATH.exists():
        raise FileNotFoundError(f"Tagging rules not found: {TAGGING_RULES_PATH}")
    return TAGGING_RULES_PATH.read_text()

async def main() -> None:
    # Call the shared run_agent function and print the result.
    print(
        await run_agent(
            PROMPT,
            tagging_rules=load_tagging_rules(),
            max_tokens=4096,
        )
    )

if __name__ == "__main__":
    asyncio.run(main())
```

What this prompt accomplishes

* Explicit role: the model is told it is an auditor for Kubernetes labels focused on FinOps governance.
* Concrete checklist: step-by-step actions — list namespaces, fetch specific resource kinds, report exact labels, evaluate against rules, flag violations, and produce a full summary.
* Anti-hallucination guidance: an explicit instruction to "Use only data returned by the tools. Do not invent labels or resources."
* Separation of concerns: the tagging rules YAML is not embedded in the user prompt. It is loaded separately and injected as a system message (see below) so the user message remains lean and task-focused.

System messages are the right place for hard constraints (for example, "do not invent labels/reports") because they are higher-precedence instructions to the model. Put non-negotiable rules and strict policy data (the tagging rules YAML) in a system message, and use the user prompt to describe the audit task itself.

mcp\_client.py
This module builds the language model client, converts MCP servers into LangChain-style tools, and runs the agent. The crucial behavior here is prepending a SystemMessage that contains the tagging rules YAML and explicit instructions the model must follow. That system-level injection defines a strict boundary the model should not cross.

```python theme={null}
# mcp_client.py
import os
from typing import Tuple
from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage
# The following conversion and agent creation helpers are assumed to be implemented
# elsewhere in the codebase:
# - convert_mcp_to_langchain_tools
async def get_mcp_tools():
    # convert_mcp_to_langchain_tools should return (tools, cleanup_coroutine)
    return await convert_mcp_to_langchain_tools(MCP_SERVERS)

def build_llm(max_tokens: int | None = None) -> ChatOpenAI:
    return ChatOpenAI(
        model_name=os.environ["OPENAI_MODEL_ID"],
        openai_api_base=os.environ.get("OPENAI_BASE_URL"),
        openai_api_key=os.environ.get("OPENAI_API_KEY"),
        temperature=float(os.getenv("OPENAI_TEMPERATURE", "0.3")),
        max_tokens=max_tokens or int(os.getenv("OPENAI_MAX_TOKENS", "512")),
    )

async def run_agent(
    prompt: str,
    max_tokens: int | None = None,
    tagging_rules: str | None = None,
) -> str:
    tools, cleanup = await get_mcp_tools()
    try:
        agent = create_agent(build_llm(max_tokens=max_tokens), tools)
        messages = []
        if tagging_rules:
            # Strict system-level guidance that the model must follow.
            messages.append(
                SystemMessage(
                    content=(
                        "Apply the tagging rules below when evaluating cluster resources. "
                        "Treat label_mappings as the only accepted keys for each required tag. "
                        "Do not invent extra policy, exclusions, or judgments beyond this YAML.\n\n"
                        f"Tagging rules (YAML):\n{tagging_rules}"
                    )
                )
            )
        messages.append(HumanMessage(content=prompt))
        result = await agent.arun({"messages": messages})
        # The agent may return a string or a structured message list; handle both.
        if isinstance(result, dict) and "messages" in result:
            return result["messages"][-1].content
        return result
    finally:
        await cleanup()
```

How run\_agent composes messages and tools

* Tools are converted from MCP servers into LangChain-like tools via `convert_mcp_to_langchain_tools(MCP_SERVERS)`.
* The LLM client is configured by `build_llm(...)` using environment variables: `OPENAI_MODEL_ID`, `OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_TEMPERATURE`, and `OPENAI_MAX_TOKENS`.
* If tagging rules are supplied, the client prepends a SystemMessage that embeds the YAML and supplies strict enforcement instructions.
* The user prompt is then supplied as a `HumanMessage`. Agents run with both messages present, so the system instructions have higher precedence.
* The function cleans up tool resources via the returned `cleanup()` coroutine regardless of success or failure.

Key takeaways

* Separate the user task (HumanMessage) from the policy and enforcement (SystemMessage). This decreases hallucinations and keeps the task prompt concise.
* Embed tagging rules and strict constraints in the system message so they are treated as non-negotiable by the model.
* Use the model only with data returned by tools; the prompt should require the agent to "report exactly as returned" to avoid invented labels or resources.
* Ensure cleanup coroutines returned by tool conversion functions are awaited to release resources.

References and next steps

* LangChain: [https://learn.kodekloud.com/user/courses/langchain](https://learn.kodekloud.com/user/courses/langchain)
* MCP (MCP for Beginners): [https://learn.kodekloud.com/user/courses/mcp-for-beginners](https://learn.kodekloud.com/user/courses/mcp-for-beginners)
* Kubernetes labels and best practices: [https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)

That completes this lesson/article. The next step is to run this agent against a real cluster (or a mocked toolset) and observe the audit outputs and any labeling violations reported.

> **[Watch Video](https://learn.kodekloud.com/user/courses/building-kubernetes-finops-ai-agent/module/5045eac8-8b03-4ecd-9213-d50eda3353c5/lesson/6388a30a-be8f-4bbf-874b-a35933eb89a9)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.