Blog

  • vExpose is back — and it’s changing

    A quick, honest note: vExpose went quiet for a while. I’m back — and the blog is changing in a way I’m genuinely excited about.

    Here’s the shift. Most technical writing shows you the strategy but hides the wiring. Going forward, vExpose does both: I state the call a technology leader has to make, then prove it in code, architecture, and benchmarks. One conviction runs through all of it — AI fails in the operating model, not the model. The tool is not the transformation.

    Two things to start with:

    • A new flagship piece, A Reference Architecture for Agentic ITSM, on safely letting a GenAI agent act in ServiceNow — with the guardrail code that makes it work.
    • A new Start Here page that organizes the best of the blog by topic, so you can jump straight to what’s useful.

    What’s coming: reference architectures, build logs with real code, and benchmarks — on a steady cadence this time. No noise, just the work. If there’s a topic you want me to dig into, just reply — I read everything.

    — Deepak

  • A Reference Architecture for Agentic ITSM: Wiring GenAI into ServiceNow Without Losing Control

    A year ago I wrote about integrating generative AI into ITSM workflows. Back then, “integration” mostly meant a chatbot that summarized tickets and drafted responses. Useful, but bounded — the model suggested, a human acted.

    That line has moved. The interesting question in 2026 isn’t whether GenAI can read a ticket; it’s whether you can safely let it close one. The moment a model can take actions in your system of record — reassign an incident, run a diagnostic, restart a service, request a change — you’ve crossed from “assistant” into “agent.” And agents in production are an infrastructure and governance problem long before they’re a model problem. This is the clearest case I know of a principle I keep coming back to: AI fails in the operating model, not the model.

    This post is the reference architecture I’d stand up to do agentic ITSM properly: enough autonomy to actually save time, enough control that I’d let it near a production ServiceNow instance. I’ll show the components, the wiring, the code that matters, and — more importantly — the parts everyone underestimates.

    The failure mode we’re designing against

    Most agentic pilots fail the same way. Someone connects a capable model directly to an API with broad credentials, it works beautifully in the demo, and then in week three it confidently takes a wrong action on a real incident because a retrieved document was stale and nothing stopped it. The problem was never the model’s intelligence. It was that the architecture had no seam between “the model decided” and “the system did.”

    Which is the whole point in one line: the tool is not the transformation. A more capable model doesn’t close this gap — it just takes the wrong action more fluently. Everything below exists to create that seam.

    The reference architecture

    Five layers, each with a single responsibility. The discipline is keeping them separate — that separation is what makes the system auditable, testable, and safe to evolve.

      Trigger  (new/updated incident, chat, webhook)
         |
         v
      Retrieval Layer      RAG over KB, CMDB, past incidents
         |
         v
      Orchestration Layer  agent loop + tool-calling
         |
         v
      Policy & Guardrail Gate  --- restricted / low confidence --->  Human-in-the-loop
         |                                                            (approval queue)
      allowed + high confidence                                            |
         |                                                              approved
         v  <-------------------------------------------------------------+
      Action Layer  (scoped ServiceNow API)
         |
         v
      Observability  (traces, evals, audit log)  --->  back to Trigger

    1. Retrieval Layer. The agent is only as good as what it knows about this incident. Ground every decision in your own data: the knowledge base, the CMDB (what is this CI, what depends on it), and — the highest-signal source most teams ignore — the resolution history of similar past incidents. Retrieval quality, not model size, is usually the difference between a useful agent and a plausible-sounding one.

    2. Orchestration Layer. The agent loop: given the incident and retrieved context, decide the next step, optionally call a tool, observe the result, repeat until resolved or escalated. This is where model-provider choice lives — and it should be swappable, not hardcoded.

    3. Policy & Guardrail Gate. The seam. Every proposed action passes through a deterministic policy check before it can touch ServiceNow. This layer is plain code, not a model — because “what is this agent allowed to do” is a decision you must be able to read, test, and audit, not one you delegate to a probability distribution. This is where your operating DNA — how decisions get made and who is trusted to act — stops being culture and becomes code.

    4. Action Layer. A narrow, scoped adapter to ServiceNow. It exposes only the specific operations the agent is permitted to perform, each backed by a least-privilege service account. The agent never gets your admin token.

    5. Observability. Full traces of every decision, tool call, and action; an immutable audit log; and continuous evaluation. If you can’t answer “why did the agent do that, and was it right,” you can’t run this in production.

    The seam, in code

    Here’s the heart of it: the orchestration layer proposes an action, and the guardrail gate decides whether it executes, gets downgraded to a human approval, or is refused. This is deliberately boring, deterministic code — that’s the point.

    from dataclasses import dataclass
    from enum import Enum
    
    class Decision(Enum):
        ALLOW = "allow"
        REQUIRE_APPROVAL = "require_approval"
        DENY = "deny"
    
    @dataclass
    class ProposedAction:
        name: str            # e.g. "restart_service", "reassign", "add_work_note"
        target_ci: str       # configuration item the action touches
        params: dict
        confidence: float    # model-reported, treated as a hint, never as truth
    
    # Policy is data you can read, diff in git, and audit — not model output.
    POLICY = {
        "add_work_note":   {"max_blast_radius": "none",   "min_confidence": 0.0},
        "reassign":        {"max_blast_radius": "ticket", "min_confidence": 0.75},
        "restart_service": {"max_blast_radius": "service","min_confidence": 0.90,
                            "require_approval_for": ["prod"]},
        # default-deny: anything not listed cannot run
    }
    
    def evaluate(action: ProposedAction, ci_env: str) -> Decision:
        rule = POLICY.get(action.name)
        if rule is None:
            return Decision.DENY                      # default deny — the safe default
        if action.confidence < rule["min_confidence"]:
            return Decision.REQUIRE_APPROVAL
        if ci_env in rule.get("require_approval_for", []):
            return Decision.REQUIRE_APPROVAL
        return Decision.ALLOW

    Three design choices are doing all the work here:

    • Default deny. An action the policy doesn’t explicitly know about cannot run. New capabilities are added deliberately, in a reviewed change to the policy, not discovered by the model at runtime.
    • Confidence is a hint, never a gate on its own. A model saying it’s 0.99 sure is not evidence. Confidence lowers autonomy when low; it never grants autonomy on its own. High-blast-radius actions in prod route to a human regardless.
    • Blast radius is explicit. Every action is classified by what it can break. That classification, not the model’s eloquence, determines how much oversight it gets.

    The action layer itself stays thin and scoped:

    import os, requests
    
    class ServiceNowAdapter:
        """Exposes ONLY whitelisted operations, backed by a least-privilege account."""
        def __init__(self):
            self.base = os.environ["SN_INSTANCE_URL"]
            self.auth = (os.environ["SN_AGENT_USER"], os.environ["SN_AGENT_TOKEN"])
    
        def add_work_note(self, sys_id: str, note: str):
            return self._patch(sys_id, {"work_notes": note})
    
        def reassign(self, sys_id: str, group: str):
            return self._patch(sys_id, {"assignment_group": group})
    
        def _patch(self, sys_id: str, body: dict):
            r = requests.patch(f"{self.base}/api/now/table/incident/{sys_id}",
                               json=body, auth=self.auth, timeout=10)
            r.raise_for_status()
            return r.json()

    Notice what’s not here: there’s no generic “run arbitrary query” or “execute” method. The agent’s power is defined by the surface area of this class, and that surface area is a design decision you make on purpose.

    Deploy it like infrastructure, not like a notebook

    The fastest way to lose trust in an AI system is to run it as a hand-configured process someone SSHed in to start. Treat the whole stack as infrastructure-as-code from day one — reproducible, version-controlled, and reviewable. The service account, its exact permissions, the policy file, and the deployment all live in git:

    # ansible: provision the least-privilege ServiceNow integration account
    - name: Ensure agent service account has ONLY scoped roles
      vars:
        agent_roles:
          - itil            # read/update incidents
          - snc_read_only   # broad read, no write
      ansible.builtin.uri:
        url: "{{ sn_instance }}/api/now/table/sys_user_has_role"
        method: POST
        user: "{{ sn_admin_user }}"
        password: "{{ sn_admin_pass }}"
        body_format: json
        body:
          user: "{{ agent_sys_id }}"
          role: "{{ item }}"
      loop: "{{ agent_roles }}"
      # No admin, no security_admin. If the agent needs more, that's a reviewed PR.

    The point isn’t Ansible specifically — it’s that “what can this agent touch” should be a diff someone approved, not tribal knowledge.

    The parts everyone underestimates

    Evaluation is the hard part, not orchestration. Wiring an agent loop is a weekend. Knowing whether it’s getting better or worse over time is the real engineering. Build a regression set of real (anonymized) incidents with known-good resolutions and run it on every prompt or model change. Without this, every “improvement” is a guess.

    Retrieval decay will hurt you silently. A KB article gets superseded, a CMDB relationship changes, and the agent starts grounding decisions in stale truth — confidently. Freshness and provenance on retrieved context matter more than the model you pick.

    Cost is an architecture decision. Running a frontier model on every ticket update is how pilots die in the budget review. Route by difficulty: cheap/local models for triage and summarization, the expensive model only for genuinely ambiguous cases. Inference routing belongs in the design, not the invoice.

    Human-in-the-loop is a feature, not a fallback. The approval queue isn’t the system admitting defeat — it’s the mechanism that lets you start with tight autonomy and earn more as the eval data proves the agent is trustworthy for a given action class. Every autonomy level you grant is a conviction with a review date: stated strongly enough to act on, dated explicitly enough that your eval data is allowed to revise it.

    How I’d actually roll this out

    1. Shadow mode first. The agent proposes actions and writes them to work notes, but executes nothing. You get real eval data at zero risk.
    2. Graduate low-blast-radius actions. Let it add work notes and reassign autonomously once shadow-mode accuracy clears your bar.
    3. Keep high-blast-radius actions human-gated — indefinitely, if that’s what the risk math says. But don’t hide behind the gate forever, either: shadow mode with no graduation path is its own failure mode — professional neutrality, saying a great deal while committing to nothing. The discipline is interpretive courage: reading the eval data everyone can see and making the call to graduate an action when it has earned it.
    4. Instrument everything from step one. You cannot retrofit observability onto an incident you can’t explain.

    The takeaway

    Agentic ITSM is not a model you buy; it’s a system you architect — because, again, AI fails in the operating model, not the model. The model is the least differentiated part — it’s swappable, and it’s improving whether you do anything or not. Your durable advantage is the seam: the deterministic, auditable, least-privilege layer between what the agent decides and what your systems actually do. Get that right and you can adopt every model improvement safely for years. Skip it and you’ve built a very expensive way to take the wrong action quickly.

    Flexibility with guardrails is the real competitive advantage in enterprise AI. Is your architecture ready to let an agent act — or only to let it talk?

    The policy-gate, ServiceNow-adapter, and provisioning code from this post are runnable on GitHub: github.com/waghmaredb/vexpose-labs. If you’re building agentic workflows on your ITSM stack, I’d like to compare notes — reach me on LinkedIn or X.

  • Integrating Generative AI in ITSM Workflows

    Integrating Generative AI in ITSM Workflows

    IT Service Management (ITSM) teams are increasingly turning to generative AI (GenAI) to streamline workflows, reduce resolution times, and improve user satisfaction. In this post, we’ll explore two architectures for integrating GenAI into platforms like ServiceNow: man-in-the-middle (human-in-the-loop) and agentic automation (autonomous AI agents).


    Why GenAI for ITSM?

    • Ticket overload: Teams handle hundreds of tickets daily, leading to burnout and delays.
    • Knowledge gaps: Agents struggle to find relevant solutions quickly.
    • Repetitive tasks: Manual triage and updates consume valuable time.

    GenAI can automate classification, suggest solutions, and even resolve incidents-but how you integrate it matters. Let’s compare two approaches.


    Architecture 1: Man-in-the-Middle (Human-in-the-Loop)

    In this approach, GenAI acts as an assistant to human agents. It analyzes tickets, suggests actions, and automates tasks but requires human approval before execution.

    Workflow Diagram:

    Use Cases:

    • High-risk scenarios (e.g., critical infrastructure changes).
    • Compliance-heavy environments (e.g., healthcare, finance).

    Implementation Steps:

    1. Set Up ServiceNow Outbound REST Integration (javascript)

    // ServiceNow Scripted REST API (Outbound)
    var request = new sn_ws.RESTMessageV2();
    request.setEndpoint('https://your-middleware.com/process-ticket');
    request.setHttpMethod('POST');
    request.setRequestBody(JSON.stringify(current));
    var response = request.execute();

    2. Build Middleware (Python/Flask Example)

    from flask import Flask, request, jsonify
    import openai

    app = Flask(__name__)
    openai.api_key = "your-api-key"

    @app.route('/process-ticket', methods=['POST'])
    def handle_ticket():
    ticket_data = request.json

    # GenAI analysis
    prompt = f"""
    Classify this ITSM ticket and suggest priority (Critical/High/Medium/Low):
    Title: {ticket_data['short_description']}
    Description: {ticket_data['description']}
    """

    response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": prompt}]
    )

    suggestion = response.choices[0].message['content']
    return jsonify({"suggestion": suggestion, "ticket_id": ticket_data['number']})

    3. Human Review & Action
    Agents review GenAI’s suggestions in a dashboard and approve/reject them.


    Architecture 2: GenAI Agentic Automation

    Here, GenAI agents act autonomously within guardrails. They analyze tickets, execute actions (e.g., resolving incidents, updating KBs), and only escalate exceptions to humans.

    Workflow Diagram:

    Use Cases:

    • Low-risk, repetitive tasks (e.g., password resets, FAQ responses).
    • High-volume environments needing 24/7 support.

    Implementation Steps:

    1. Autonomous Ticket Resolution with Python

    import requests
    import openai

    def resolve_ticket_automatically(ticket_id):
    # Fetch ticket from ServiceNow
    snow_url = f"https://instance.service-now.com/api/now/table/incident/{ticket_id}"
    headers = {"Accept": "application/json"}
    auth = ("admin", "password")
    response = requests.get(snow_url, headers=headers, auth=auth)
    ticket = response.json()['result']

    # GenAI analysis
    prompt = f"""
    Resolve this ticket autonomously if possible. Provide a solution and mark as closed.
    Ticket: {ticket['description']}
    """

    ai_response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": prompt}]
    )

    # Auto-resolve if confidence is high
    if "reset password" in ai_response.choices[0].message['content'].lower():
    update_data = {
    "state": "6", # Resolved
    "close_notes": "Automatically resolved: Password reset instructions sent."
    }
    requests.patch(snow_url, json=update_data, headers=headers, auth=auth)

    2. Guardrails for Safety

    • Limit permissions (e.g., agents can’t modify user roles).
    • Log all actions for auditing.
    • Escalate tickets containing keywords like “outage” or “data breach” to humans.

    Key Considerations

    Combined Architecture Diagram:


    Best Practices for Both Architectures

    • Data Privacy: Mask PII/PHI in tickets before sending to GenAI APIs.
    • Feedback Loops: Let agents rate AI suggestions to improve models.
    • Tooling: Use frameworks like LangChain for complex workflows.

    Conclusion

    Whether you choose man-in-the-middle or agentic automation depends on your risk tolerance and use case. Start with a hybrid approach: use autonomous agents for simple tasks (e.g., FAQs) and human-in-the-loop for critical workflows. As trust in the system grows, expand automation cautiously.

    GenAI is set to redefine ITSM, propelling organizations from automation to true intelligence. The journey is just beginning-those who embrace this shift will not only optimize IT operations but also unlock new levels of agility, resilience, and innovation. The future of ITSM is not just faster or cheaper-it’s smarter, more adaptive, and profoundly more human.

  • The Evolution of IT Infrastructure: Why Disaggregated Platforms Are the Future for GenAI

    The Evolution of IT Infrastructure: Why Disaggregated Platforms Are the Future for GenAI

    Over the past two decades, enterprise IT architecture has undergone a dramatic transformation. What began as siloed, three-tier environments has evolved through converged and hyper-converged infrastructure (HCI) to today’s cutting-edge disaggregated platforms. This journey is more than a story of hardware innovation-it’s the foundation for the next era of data-driven business, especially as generative AI (GenAI) workloads reshape the technology landscape.

    From Three-Tier to Hyper-Converged: The Drive for Simplicity

    Three-tier architecture-with separate compute, storage, and networking-offered flexibility, but at the cost of complexity and inefficiency. IT teams often faced overprovisioning, stranded resources, and operational headaches.

    The arrival of converged infrastructure bundled these elements into pre-validated stacks, simplifying procurement and deployment. Yet, the fundamental silos remained, limiting agility and resource utilization.

    Hyper-converged infrastructure (HCI) took consolidation further by merging compute and storage into modular nodes managed by software. According to Fortune Business Insights, the global HCI market is projected to reach $65 billion by 2029, reflecting enterprises’ desire for simplified management and scalability. However, HCI’s tightly coupled design makes it difficult to scale compute and storage independently-a critical limitation for today’s AI and data-intensive workloads.

    The Rise of Disaggregated Platforms: Built for GenAI

    Enter disaggregated infrastructure: an architecture that decouples compute, storage, and networking into independent resource pools. This approach is rapidly gaining traction, with the composable/disaggregated infrastructure market expected to triple to over $19 billion by 2029 (MarketsandMarkets).

    Why Disaggregation Matters-Especially for GenAI

    1. Independent Scaling GenAI workloads are unpredictable and data-hungry. Disaggregated platforms allow organizations to scale storage for massive datasets or add GPU-rich compute nodes as needed-without unnecessary overprovisioning.
    2. Superior Resource Utilization Dell reports that disaggregated architectures can deliver “orders of magnitude higher” core utilization and reduce server and software licensing costs by up to 50%.
    3. Performance for AI Technologies like NVMe-over-Fabrics and CXL memory pooling enable low-latency, high-throughput access to data-keeping GPUs saturated and AI pipelines moving at full speed.
    4. Hybrid and Edge Flexibility Disaggregated storage and compute pools can be orchestrated across on-premises, cloud, and edge environments, supporting the distributed nature of modern AI applications.

    Real-World Impact

    Industry leaders are moving fast:

    • Dell’s PowerEdge servers offer modular, independently scalable resources.
    • Many vendors/startups are rapidly evolving their portfolio to embrace disaggregation, making it the new default for enterprises scaling GenAI and other advanced workloads.
    • Edge AI is now viable, with lightweight models running inference on disaggregated clusters far from the data center.

    As Travis Vigil Vigil, SVP at Dell, puts it: Disaggregation isn’t just about hardware-it’s about rethinking the entire data center ecosystem to unlock performance and control.

    Looking Ahead

    The shift to disaggregated infrastructure is not just a technological trend-it’s a strategic imperative for organizations embracing GenAI and next-generation workloads. By enabling independent scaling, maximizing resource efficiency, and supporting hybrid deployment models, disaggregated platforms are redefining what’s possible in the data center.

    In the age of AI, flexibility is the ultimate competitive advantage. Is your infrastructure ready?

    Let’s connect and discuss how your organization can leverage disaggregated architectures to accelerate GenAI and future-proof your digital transformation.

  • Keep a container alive to troubleshoot it

    Containers are a great way to package code and dependencies in a portable package. This is fantastic in a world where dependencies keep getting bigger and more complex. But we are not here to discuss their benefits. If Google brought you here, more than likely you know already what a container is and you are trying to troubleshoot one. If that’s the case, I hope you find useful the tip I am about to tell you.

    To understand the solution we must first remember that containers are designed to run a task as soon as they start. You define such task with either CMD or ENTRYPOINT. The problem is that if the task dies the container dies too and you can’t log in into it to find out what happened. Sometimes “docker logs” will give you a hint of what’s happening but other times you wish you could simply log in to the container and check things out, but of course you can’t because the container is dead

    A solution I have become fond of is to override the ENTRYPOINT with a process that keeps the container alive and then open a terminal session into it. Let me give you an example.

    IMPORTANT: I am using Podman but all of this will work perfectly fine with Docker as well. This is the “Dockerfile“. Notice how the main task for this container is “python app.py

    FROM python:3.10.5
    WORKDIR /app
    COPY . /app
    RUN pip install --no-cache-dir -r requirements.txt
    EXPOSE 7860
    CMD ["python", "app.py"]

    This is the code contained in “app.py“. It is a simple Gradio chatbot. It gets the users prompt and sends an API call to to get a response.

    import gradio as gr
    import os, requests, import urllib3
    urllib3.disable_warnings()
    
    appurl = os.environ["APP_URL"]
    
    def give_response(query, history):
        payload = {"query": query}
        response = requests.post(appurl, json=payload)
        return response.json()["response"]
    
    demo = gr.ChatInterface(
                  give_response,
                  type = "messages",
                  title="My first Chatbot",
                  description="Ask me a question, don't be shy")
    
    demo.launch(server_name="0.0.0.0")

    As you can see the code requires us to define an environment variable “APP_URL” with the URL to send the request to. Let’s say that we don’t define the variable and attempt to run the container.

    pi@piper1:~$ podman run -d -p 7860:7860 localhost/blog:v1
    19dc02db0b2e06c51756c7f55a3eb861c11f831a21574efdcd2e00081c9191a1
    pi@piper1:~$ podman ps -a
    CONTAINER ID  IMAGE              COMMAND        CREATED        STATUS      NAMES
    19dc02db0b2e  localhost/blog:v1  python app.py  3 seconds ago  Exited (1)  dry_rice

    As expected the container fails but here is the trick … we run it again but we use the “–entrypoint” argument to run “tail -f /dev/null“. The container is not changing we are simply overriding “python app.py” with this “tail” command which stays running and in doing so keeps the container alive. Notice the “single quote” around the square brackets.

    pi@piper1:~$ podman run -d --entrypoint='["tail", "-f", "/dev/null"]' -p 7860:7860 localhost/blog:v1
    e1031d4c2ecbf3d4ec244aef262e71fb2f4e227154b6e22eabf634e4e053f4a0
    pi@piper1:~$ podman ps
    CONTAINER ID  IMAGE              STATUS        PORTS                   NAMES
    e1031d4c2ecb  localhost/blog:v1  Up 6 seconds  0.0.0.0:7860->7860/tcp  sour_soup

    Now we can login into the container and do whatever checks we need to do. We can even run “python app.py” and see what’s going on live.

    pi@piper1:~$ podman exec -it e1031d4c2ecb /bin/bash
    root@e1031d4c2ecb:/app# ls -l
    total 12
    -rw-r--r-- 1 root root 518 Mar 24 04:35 Dockerfile
    -rw-r--r-- 1 root root 566 Mar 24 04:35 app.py
    -rw-r--r-- 1 root root 897 Mar 24 04:35 requirements.txt
    root@e1031d4c2ecb:/app# python3 app.py
    Traceback (most recent call last):
      File "/app/app.py", line 7, in <module>
        appurl = os.environ["APP_URL"]
      File "/usr/local/lib/python3.10/os.py", line 679, in __getitem__
        raise KeyError(key) from None
    KeyError: 'APP_URL'

    This was a simplistic example but you get point. There could be a file or a path missing, a typo, a permissions issue … by using this trick you can troubleshoot interactively inside the container. Once you know what the issue is you can fix your files and rebuild the container image.

    The next question is, can you do this in Kubernetes as well? Yes, you can add the “command” to the “Deployment”, not to the “Pod”. This is a list of strings as you see in the last line in the simplified manifest below.

    apiVersion: v1
    kind: Deployment
    metadata:
      name: blog
    spec:
      containers:
      - name: blog-container
        image: debian
        command: ["tail", "-f", "/dev/null"]

    As soon as I save the changes to the deployment manifest, Openshift creates a new pod and kills the old one. Then I can terminal into the new pod and browse around. Notice I can even launch the application from the terminal session.

    $ pwd         
    /app
    $ ls -l
    total 12
    -rw-r--r--. 1 root root 518 Mar 24 04:35 Dockerfile
    -rw-r--r--. 1 root root 566 Mar 24 04:35 app.py
    -rw-r--r--. 1 root root 897 Mar 24 04:35 requirements.txt
    $ ps -ef
    UID          PID    PPID  C STIME TTY          TIME CMD
    1000770+       1       0  0 00:00 ?        00:00:00 tail -f /dev/null
    1000770+       7       0  0 00:00 pts/0    00:00:00 sh -i -c TERM=xterm sh
    1000770+      13       7  0 00:00 pts/0    00:00:00 sh
    1000770+      90      13  0 00:02 pts/0    00:00:00 ps -ef
    
    $ python3 app.py
    * Running on local URL:  http://0.0.0.0:7860
    
    To create a public link, set `share=True` in `launch()`.

    Of course, this is only intended for troubleshooting. Once you find out what’s wrong you can fix the image and manifest and deploy them again.

  • GenAI as a Replacement for Enterprise Search: Revolution or Evolution?

    GenAI as a Replacement for Enterprise Search: Revolution or Evolution?

    Enterprise search has long been the backbone of organizational knowledge discovery, enabling employees to sift through vast troves of internal data-emails, documents, reports, and more. Yet, as information volume and complexity have exploded, traditional search systems have struggled to keep pace. Enter Generative AI (GenAI): a transformative technology that promises not just incremental improvements, but a fundamental reimagining of how enterprises access and leverage information.

    The Traditional Enterprise Search Challenge

    Legacy enterprise search platforms typically rely on keyword-matching and index-based algorithms. While effective for straightforward queries, these systems falter when faced with:

    • Ambiguous or complex natural language queries
    • Unstructured or poorly tagged data
    • Industry-specific jargon and synonyms
    • The need for contextual, role-based, or personalized results
    • Multi-language demands and adaptive access controls

    The result? Knowledge workers spend an estimated 2.5 to 3.6 hours per day searching for information, leading to frustration and lost productivity.

    How GenAI Transforms Enterprise Search

    GenAI, powered by large language models (LLMs) and advanced natural language processing (NLP), addresses these pain points in several critical ways:

    1. Deep Contextual Understanding and Intent Detection

    Unlike traditional search, GenAI interprets the context and underlying intent behind user queries. Whether an employee asks, “How do I reset my password?” or “I forgot my password, can you help?”-GenAI recognizes the shared intent and delivers precise, relevant answers. This semantic understanding extends to complex, multi-step, or follow-up queries, enabling conversational and intuitive information retrieval.

    2. Personalization and Adaptive Learning

    GenAI-powered search systems learn from user profiles, search histories, and real-time interactions, tailoring results to individual roles and preferences. Over time, this leads to hyper-personalized experiences, with the system anticipating user needs and improving with every interaction.

    3. Mastery of Unstructured and Multilingual Data

    GenAI excels at parsing unstructured data-emails, PDFs, images-transforming them into structured, searchable formats. It also supports multilingual and cross-domain queries, making it invaluable for global enterprises with diverse data sources.

    4. Automation and Decision Support

    Beyond search, GenAI can automate clerical tasks-summarizing documents, drafting emails, compiling reports-freeing employees to focus on higher-value work. It can synthesize insights across multiple sources, supporting faster and more informed decision-making.

    5. Enhanced Security and Compliance

    Modern GenAI enterprise search solutions integrate robust access controls and compliance features, ensuring sensitive information is only accessible to authorized users. AI can also detect anomalies and potential security threats by analyzing usage patterns.

    Real-World Impact

    Leading organizations are already leveraging GenAI to revolutionize internal knowledge centers, streamline customer service, and optimize business processes. The result is a measurable boost in productivity, faster decision cycles, and significant cost savings.

    Challenges and Considerations

    Despite its promise, GenAI is not a panacea. Key challenges include:

    • Data Quality and Integration: Success depends on clean, well-organized data and seamless integration with existing systems.
    • Hallucination Risk: LLMs may generate plausible but incorrect answers if not properly constrained by retrieval-augmented generation (RAG) or domain-specific fine-tuning.
    • Security and Governance: Ensuring robust access controls and preventing prompt-based security bypasses remain ongoing concerns.
    • Infrastructure Readiness: Many enterprises lack the foundational data infrastructure and skilled workforce needed for successful GenAI deployment.

    Is GenAI a Replacement or an Evolution?

    While GenAI dramatically elevates enterprise search, it is best viewed not as a wholesale replacement, but as an evolutionary leap. GenAI augments and, in many cases, supersedes traditional search by delivering context-aware, conversational, and highly relevant results. However, its success hinges on thoughtful implementation, robust data governance, and continuous improvement.

    “GenAI is no magic bullet, but applied in the right places it has potential to improve enterprise search… As the technology evolves, its ability to further enhance search capabilities will grow, offering even more refined solutions to complex search challenges.”

    The Future: From Search to Knowledge Discovery

    As GenAI matures, the line between search and intelligent knowledge discovery will blur. Enterprises that invest in GenAI-powered search today are not just making information easier to find-they are laying the groundwork for a future where knowledge is proactively surfaced, synthesized, and delivered in context, driving innovation and competitive advantage.

    In summary: GenAI is not just a better search engine-it is the catalyst for a new era of enterprise intelligence. For organizations ready to embrace this shift, the rewards are substantial: more empowered employees, faster insights, and a culture of data-driven excellence.

  • Podman build fails with /etc/passwd: permission denied

    A short post today to share an issue that troubled me for a while. Hopefully it can help you too.

    Basically I was trying to build a new container image using Podman in Ubuntu. These are my versions:

    • Ubuntu 24.04.2 LTS
    • podman version 4.9.3

    My Dockerfile is straight forward

    FROM python:3.10.5
    WORKDIR /app
    COPY . /app
    RUN pip install --no-cache-dir -r requirements.txt
    CMD ["python", "app.py"]

    When I tried to create the container image I get the following output

    (env) user1@mytpl:~/Desktop/myapp$ podman build -t myapp:v1 .
    STEP 1/6: FROM python:3.10.5
    Resolved "python" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
    Trying to pull docker.io/library/python:3.10.5...
    Getting image source signatures
    Copying blob 588423e31bcf done   |
    Copying blob 001c52e26ad5 done   |
    Copying blob 2068746827ec done   |
    Copying blob d9d4b9b6e964 done   |
    Copying blob 8a335986117b done   |
    Copying blob 9daef329d350 done   |
    Copying blob ecb6a3f01c0d done   |
    Copying blob 00d40f20f0cf done   |
    Copying blob 5d588b4f3b55 done   |
    Error: creating build container: copying system image from manifest list: writing blob: adding layer with blob "sha256:001c52e26ad57e3b25b439ee0052f66           92e5c0f2d5d982a00a8819ace5e521452": processing tar file(open /etc/passwd: permission denied): exit status 1

    In the end I tracked it down to a service called “mfetpd.service” which appears to be some Trellix endpoint security software.

    (env) user1@mytpl:~/Desktop/myapp$ sudo systemctl status mfetpd.service
    ● mfetpd.service - Trellix Endpoint Security for Linux Threat Prevention
         Loaded: loaded (/usr/lib/systemd/system/mfetpd.service; enabled; preset: enabled)
         Active: active (running) since Mon 2025-03-24 14:35:44 +08; 5min ago
           Docs: man:mfetpd(8)
        Process: 30546 ExecStartPre=/opt/McAfee/ens/tp/scripts/aac-control-wrapper.sh systemd (code=exited, status=0/SUCCESS

    The solution was to temporarily stop this service to build the container image

    sudo systemctl stop mfetpd.service
    podman build ...
    sudo systemctl start mfetpd.service

    Once the image “python:3.10.5” layer is downloaded I am able to build more containers using the same version of Python without having to pause the “mfetpd” service which makes my security team happy.

    I hope this helps you same some of your precious time!

  • Infrastructure as Code (IaC) Security Best Practices

    Infrastructure as Code (IaC) Security Best Practices

    Infrastructure as Code (IaC) has revolutionized how we provision and manage infrastructure, enabling speed, repeatability, and scalability. However, as with any code, IaC introduces new security considerations. Misconfigurations, exposed secrets, and lack of visibility can lead to significant risks in production environments.

    This guide explores actionable best practices for securing your Terraform, Ansible, and other IaC pipelines-helping you build robust, compliant, and resilient infrastructure. Sample code and how-to notes included!


    1. Treat IaC Like Application Code

    Just as you would with application code, store your IaC in version control systems (e.g., Git). This enables:

    • Change tracking: Who changed what, when, and why.
    • Peer reviews: Enforce code reviews and approvals before merging.
    • Rollback: Revert to previous known-good states if issues arise.

    Tip:
    Use branch protection rules and require pull request reviews for all changes.


    2. Secrets Management

    Never hard-code secrets or sensitive data (API keys, passwords, certificates) in your IaC files. Instead:

    • Use secret management tools like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.
    • Integrate secrets into your pipelines at runtime, not in code.
    • Leverage environment variables or encrypted files for sensitive values.

    Sample: Fetching Secrets from Vault in Terraform

    provider "vault" {
      address = "https://vault.example.com"
    }
    
    resource "vault_generic_secret" "example" {
      path = "secret/data/myapp"
    }
    
    output "db_password" {
      value = vault_generic_secret.example.data["password"]
    }

    How-to:

    • Replace https://vault.example.com with your Vault server address.
    • The vault_generic_secret resource fetches secrets dynamically.
    • Output blocks can reference secrets securely-never hardcode them!

    Sample: Encrypting Secrets in Ansible with ansible-vault

    - hosts: all
      vars_files:
        - secrets.yml
      tasks:
        - name: Use secret password
          debug:
            msg: "The password is {{ secret_password }}"

    How-to:

    • Create and encrypt the secrets file: ansible-vault create secrets.yml
    • Reference the encrypted file in your playbook under vars_files.
    • Access secrets in tasks using Jinja2 templating, e.g., {{ secret_password }}.

    3. Automated Security Scanning

    Integrate security tools into your CI/CD pipeline to catch misconfigurations and vulnerabilities early:

    Sample: Terraform Security Scanning with tfsec in GitHub Actions

    name: Terraform Security Scan
    
    on: [push]
    
    jobs:
      tfsec:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: Run tfsec
            uses: aquasecurity/tfsec-action@v1
            with:
              tfsec_version: 'latest'

    How-to:

    • Add this workflow to your .github/workflows directory.
    • The scan will fail the build if critical security issues are found, helping you catch problems early.

    4. Principle of Least Privilege

    Ensure that your IaC tools, pipelines, and the resources they provision follow the principle of least privilege:

    • Limit IAM permissions for automation accounts.
    • Avoid using overly broad roles or root accounts.
    • Regularly audit permissions and remove unnecessary access.

    5. Policy as Code

    Define and enforce security and compliance policies programmatically:

    • Use tools like Open Policy Agent (OPA), Sentinel (for Terraform Cloud), or Conftest.
    • Enforce rules such as “no public S3 buckets,” “encryption enabled,” or “no hardcoded credentials.”

    Example:
    OPA policies can block deployments that violate security standards before they reach production.


    6. Continuous Monitoring and Drift Detection

    Even after deployment, infrastructure can drift from the desired state:

    • Use tools like Terraform Cloud, AWS Config, or Driftctl to detect and remediate drift.
    • Set up alerts for unauthorized changes and automate remediation where possible.

    7. Regular Reviews and Updates

    IaC modules and dependencies evolve-so should your security practices:

    • Schedule regular reviews of your IaC codebase and third-party modules.
    • Update modules to patch vulnerabilities and leverage new security features.
    • Document your security practices and train your team.

    Conclusion

    Securing Infrastructure as Code is not a one-time task, but a continuous process woven into your development and deployment lifecycle. By treating IaC like application code, managing secrets securely, automating scanning, enforcing least privilege, and using policy as code, you can drastically reduce risk and build trust in your automation.

  • How to Benchmark Enterprise Storage: Fio and Vdbench Explained

    How to Benchmark Enterprise Storage: Fio and Vdbench Explained

    Enterprise storage arrays from vendors like Dell EMC, NetApp, and Pure Storage are the foundation of mission-critical IT environments. Ensuring these systems deliver consistent, high performance under real-world workloads is essential for application reliability and business continuity. In this post, we’ll explore how to benchmark enterprise storage using two industry-leading tools- Fio and Vdbench– with practical configuration examples and best practices.

    Why Benchmarking Enterprise Storage is Unique

    Enterprise arrays are not just fast disks-they’re complex systems with:

    • Multiple controllers and cache layers
    • Advanced data protection (RAID, erasure coding)
    • High-speed protocols (Fibre Channel, iSCSI, NVMe-oF)
    • Storage tiering and virtualization
    • Multi-protocol (block, file) support

    Benchmarking these systems is different from testing a single SSD or HDD. You must simulate production-like workloads, test at scale, and observe performance under both normal and failure conditions.

    Best Practices for Enterprise Storage Benchmarking

    • Simulate real-world workloads: Use realistic mixes of random/sequential I/O, read/write ratios, and block sizes.
    • Test at scale: Ensure test data sets exceed cache sizes and run with sufficient concurrency.
    • Run long-duration tests: Observe steady-state performance, not just short-term cache hits.
    • Monitor the full stack: Track storage, network, and server metrics.
    • Document everything: Record hardware/software versions, configurations, and test parameters.
    • Include failure scenarios: Simulate controller or network failures to test resilience.

    Fio: Flexible I/O Tester

    Fio is a versatile, scriptable tool ideal for generating a wide range of I/O workloads against enterprise storage.

    Example Fio Job File for Enterprise Storage
    text[global]
    ioengine=libaio
    direct=1
    rw=randrw
    rwmixread=70
    bs=8k
    iodepth=64
    numjobs=8
    size=100G
    runtime=3600
    time_based
    group_reporting
    filename=/dev/sdx # Replace with your LUN or device

    [verify]
    verify=crc32

    What this does:

    • Simulates a 70% read/30% write random workload with 8KB blocks.
    • Runs 8 parallel jobs with a queue depth of 64 for 1 hour.
    • Uses direct I/O to bypass the OS cache.
    • Verifies data integrity with CRC32.

    Run it with:

    sudo fio enterprise_test.fio

    Tips:

    • Use multiple devices or files to simulate multi-volume workloads.
    • Adjust concurrency (numjobs, iodepth) to match your environment.
    • Use --output-format=json for detailed reporting.

    Vdbench: Enterprise Workload Generator

    Vdbench is designed for complex, multi-host enterprise storage validation and offers granular workload definition and data validation.

    Example Vdbench Configuration
    text# Storage Definitions for multiple LUNs
    sd=sd1,lun=/dev/sdb,size=100g,openflags=o_direct
    sd=sd2,lun=/dev/sdc,size=100g,openflags=o_direct
    sd=sd3,lun=/dev/sdd,size=100g,openflags=o_direct
    sd=sd4,lun=/dev/sde,size=100g,openflags=o_direct

    # Workload Definition: 67% read, 33% write, 8KB random
    wd=wd1,sd=(sd1-sd4),xfersize=8k,rdpct=67,seekpct=100

    # Run Definition: max I/O, 24 hours, 1s reporting
    rd=rd1,wd=wd1,iorate=max,elapsed=86400,interval=1

    Run it with:

    vdbench -f enterprise_vdbench.conf

    Advanced tips:

    • Use the hd section to define multiple hosts for distributed testing.
    • Simulate failures (e.g., disconnect a path or controller) during the run to observe failover behavior.
    • Use fsd and fwd for NAS/file workloads.

    Key Steps for Success

    1. Profile your workload: Know your application’s I/O patterns. (capture the workload IO pattern)
    2. Prepare your environment: Use dedicated test LUNs/volumes.
    3. Configure your tools: Use Fio or Vdbench job files that match your workload.
    4. Run and monitor: Capture storage, host, and network metrics.
    5. Analyze results: Look for steady-state performance, latency spikes, and the impact of failures.
    6. Document and repeat: Ensure tests are reproducible and results are transparent.

    Conclusion

    Benchmarking enterprise-class storage is about more than just peak numbers-it’s about understanding how your array performs under pressure, during failures, and with your real workloads. Tools like Fio and Vdbench provide the flexibility, power, and validation features needed for accurate, actionable results. By following best practices and using realistic configurations, you can ensure your storage infrastructure is ready for the demands of the modern enterprise.

    References:

    • SNIA Storage Performance Testing Guide
    • Fio and Vdbench Official Documentation
  • Ubuntu VMs get same IP from DCHP in vSphere

    The problem

    This is a quick post to present a solution to this problem that bugged me for a while. If you are using Ubuntu as the guest OS for vSphere virtual machines you might encounter this problem. I was running Ubuntu 22.04 but I think it might be the same problem for other versions.

    Every new VM I created was getting the same IP address from the DCHP server even though the MAC address was different.

    The explanation

    I had a Ubuntu VM template configured for DHCP as follows:

    root@albmtest:~# cat /etc/netplan/99-netcfg-vmware.yaml
    # Generated by VMWare customization engine.
    network:
      version: 2
      renderer: networkd
      ethernets:
        ens160:
          dhcp4: yes
          dhcp4-overrides:
            use-dns: false
          nameservers:
            addresses:
              - 172.24.1.10

    The explanation is that by default Ubuntu’s Netplan doesn’t use the MAC address for DHCP. By default it uses the “machine-id”. This is explained in this paragraph of the Netplan documentation.

    So, unless you set “dhcp-identifier” to “mac“, by default netplan uses “machine-id“. You can find this in “/etc/machine-id” inside the VM. But as I found out, it transpires VMware doesn’t change regenerate “/etc/machine-id” in the guest by default. When you put those 2 default behaviors together you get a problem.

    The Solution

    You can solve this by either:

    • erasing the machine-id before creating a template by running “echo > /etc/machine-id
    • editing the netplan yaml file to force it to use “mac” addresses for DHCP
    root@albmtest:~# cat /etc/netplan/99-netcfg-vmware.yaml
    # Generated by VMWare customization engine.
    network:
      version: 2
      renderer: networkd
      ethernets:
        ens160:
          dhcp4: yes
          dhcp4-overrides:
            use-dns: false
          dhcp-identifier: mac
          nameservers:
            addresses:
              - 172.24.1.10

    If you modify “netplan” don’t forget to “apply” to make sure any changes you make are activated.

    root@albmtest:~# netplan apply

    Final TIP: when you are testing your changes, you can force the DHCP client to renew the IP like this

    root@albmtest:~# dhclient -r

    I hope this post saved you some trouble. Good luck!