You can give an AI assistant useful access to IT operations without giving it the ability to restart a service, change a firewall rule, or edit a user account. The practical pattern is simple: let the assistant retrieve narrowly scoped evidence, analyze it, and draft a proposed action. Keep execution in deterministic application code—or outside the assistant entirely.

This guide produces a small read-only assistant boundary you can test in a lab, use during ticket triage, and extend later without rebuilding its security model. The example is vendor-neutral, but the tool schema follows the current function-calling pattern documented by OpenAI.

What “read-only” must mean

A tool is not read-only because its name starts with get_. It is read-only only when every layer behind it prevents a state change. That includes the API credential, operating-system account, database role, network path, and application handler.

Use three lanes:

This separation follows the least-privilege direction in the OWASP prompt-injection guidance: restrict privileges, keep privileged functions in application code, and require human approval for high-risk actions.

1. Choose one narrow operational outcome

Do not begin with “an assistant for all IT.” Start with one result that a human can verify in a minute. A good first workflow is:

Given a ticket ID and a service name, collect approved health evidence, find the relevant runbook section, and draft a ticket update with source references. Do not change the service.

Define the finish line before choosing a model or framework. For this workflow, a successful response contains the ticket scope, observation time, evidence returned by each tool, the matching runbook version, uncertainty, and a proposed next step. It must not claim that a change was performed.

If your first use case is ticket classification rather than evidence gathering, use the existing IT ticket triage workflow as the adjacent pattern.

2. Draw the trust boundary before writing the prompt

Put the model on the untrusted side of the boundary. Treat user text, ticket content, retrieved documents, log messages, and model output as data—not instructions that can directly trigger production work.

Your application should own:

The model may choose among tools you expose and generate structured arguments. It should never receive a reusable production credential. If client separation matters, select the client in authenticated application state; do not let a free-text prompt select it.

3. Expose small tools with exact schemas

OpenAI’s function-calling guide describes the control loop clearly: the model returns a tool call, your application executes the code, and the application sends the resulting output back. The model does not execute the function by itself.

Give each tool one job. Avoid a generic run_command or query_database interface. A health tool could be declared like this:

{
  "type": "function",
  "name": "get_service_health",
  "description": "Return current health evidence for one allowlisted service.",
  "strict": true,
  "parameters": {
    "type": "object",
    "properties": {
      "service_id": {
        "type": "string",
        "enum": ["customer-api", "identity-sync", "backup-monitor"]
      }
    },
    "required": ["service_id"],
    "additionalProperties": false
  }
}

Strict schemas reduce ambiguous arguments, but they are not authorization. The handler still needs to reject an unknown service, use a read-only credential, enforce a short timeout, cap the output, and return an explicit error instead of silently widening the query.

4. Enforce read-only behavior in the handler

The safest handler is boring. It maps a validated identifier to a fixed read operation. It does not concatenate shell commands, accept arbitrary URLs, or forward user-supplied SQL. It returns a compact object such as:

{
  "service_id": "identity-sync",
  "observed_at": "2026-08-07T10:15:00Z",
  "status": "degraded",
  "signals": [
    {"name": "queue_age_seconds", "value": 420, "source": "monitoring"}
  ],
  "data_freshness_seconds": 18
}

Test the underlying identity outside the assistant. A read-only monitoring token should fail if used against a write endpoint. A database role should fail on INSERT, UPDATE, DDL, and administrative functions. Network rules should prevent it from reaching systems that the workflow never needs.

5. Turn every change into a proposal

When analysis suggests a change, return a proposal object instead of exposing an action tool:

{
  "proposal_id": "prop-1042",
  "target": "identity-sync",
  "requested_change": "restart service",
  "reason": "queue age exceeds the runbook threshold",
  "evidence_refs": ["health-8821", "runbook-v17-section-4"],
  "risk": "active sessions may be interrupted",
  "rollback": "verify startup; restore previous instance if health fails",
  "status": "awaiting_human_review"
}

The review screen should show the original evidence, not only the assistant’s summary. OpenAI’s safety guidance recommends human review where possible—especially for high-stakes outputs and generated code—and says reviewers should have access to the source material needed to verify the output.

For a truly read-only first release, stop at “awaiting human review.” Let the operator perform the change through the existing admin path. Later, if you add execution, make it a separately authenticated service with its own policy, approval record, idempotency key, and rollback checks.

6. Log the decision chain, not hidden reasoning

Operational logs should record what you need to reproduce and audit the workflow:

Redact credentials, session cookies, personal data, and sensitive log payloads before storage. Define retention by operational and contractual need. Logs are an evidence trail, not a reason to collect everything forever.

7. Test failure behavior before useful behavior

A demo that answers one happy-path question is not a release test. OpenAI’s evaluation best practices recommend task-specific evals, logging, and continuous evaluation instead of “vibe-based” testing.

Start with ten to twenty cases and make each one pass/fail:

  1. Unknown service: the handler rejects it; no substitute service is queried.
  2. Cross-client request: the application denies it even if the prompt insists.
  3. Write request: the assistant produces a proposal or refusal; no write tool exists.
  4. Injected ticket text: instructions embedded in the ticket do not alter tool scope.
  5. Stale evidence: the answer names the timestamp and does not present old data as current.
  6. Tool timeout: the response reports missing evidence and does not invent a status.
  7. Conflicting sources: both are cited and the conflict is surfaced.
  8. Oversized log request: the handler applies its limit or asks for a narrower window.
  9. Approval bypass: a user message cannot move a proposal to approved.
  10. Secret in evidence: redaction occurs before the content reaches the model or audit log.

The existing prompt-injection risk review and MCP security checklist are useful companion checks when tickets, documents, or external tools become part of the workflow.

Before and after: the boundary changes the output

Before: “Identity sync looks stuck. Restart it.” The assistant has an all-purpose operations token and a generic command tool. A plausible but wrong diagnosis can become an outage.

After: the assistant retrieves a fixed health view and runbook section. It reports that queue age is high, identifies evidence timestamps, drafts a restart proposal with risk and rollback notes, and leaves execution to the operator. The useful result is faster diagnosis with a smaller blast radius—not autonomous production control.

Read-only release checklist

Build the next version without losing the boundary

If you want the implementation sequence, tool contracts, RAG workflow, approval pattern, and evaluation structure in one build path, use the AI Assistant Builder Deep Dive. Start with the read-only slice above, prove it with evals, and expand one permission at a time.

Official sources