Agent integration bundle
Use this bundle when Drasi is feeding AI agents, MCP clients, dashboards, agent workflows, or other consumers that subscribe to live query results.
Primary pattern
For agent-facing live data, prefer the MCP Reaction when it matches the use case. It exposes query result resources to MCP clients and supports resource subscription notifications.
Current docs checked for this package describe:
- Streamable HTTP transport with SSE fallback.
- Protocol version
2025-03-26with compatibility for2024-11-05. - Query resource URIs shaped as
drasi://query/{queryId}. notifications/resources/updatednotifications for added, updated, and deleted operations.
Verify current docs before implementing because MCP and Drasi are both moving quickly.
Consumer-side reference architecture
This section defines the SDK-agnostic architecture every consumer needs, whether you are building a Microsoft Agent Framework agent, a Semantic Kernel plugin, a custom Rust service, or a Python daemon. The patterns below are independent of any particular AI SDK or programming language.
Component architecture
Every Drasi MCP consumer has the same logical components:
+-----------------------------------------------------------+
| Consumer Process |
| |
| +----------------+ +----------------+ +-----------+ |
| | MCP Transport |--->| MCP Session |--->| Router | |
| | (Streamable | | (init, list, | | (dispatch| |
| | HTTP / SSE) | | read, sub) | | by type)| |
| +----------------+ +----------------+ +-----+-----+ |
| | |
| +----------------------------+------+ |
| | +-------------------+----+ | |
| v v v v | |
| +--------+ +--------+ +-----------+ | |
| |Handler | |Handler | ... | Error | | |
| | A | | B | | Guard | | |
| +--------+ +--------+ +-----------+ | |
| | | | |
| v v | |
| +--------+ +--------+ | |
| | Agent | | Queue /| | |
| |Memory | | Store | | |
| +--------+ +--------+ | |
+-----------------------------------------------------------+Component responsibilities:
| Component | Role | Framework-agnostic? |
|---|---|---|
| MCP Transport | Manages HTTP/SSE connection to the Drasi MCP Reaction endpoint. Handles connection lifecycle, TLS, and transport-level reconnection. | Yes -- every consumer needs one. |
| MCP Session | Wraps the transport with MCP protocol handshake (initialize), resource operations (list, read), and subscription management (subscribe, unsubscribe). | Yes -- the MCP SDK (any language) provides this. |
| Notification Router | Receives raw notifications/resources/updated from the session, parses the Drasi-specific operation and data fields, and dispatches to registered handlers by change type. |
Yes -- this is bespoke to Drasi and must handle the non-standard params. |
| Handlers | Implement the per-change-type logic: log, store, queue, or inject into agent state. A consumer may have multiple handlers attached to one router. | Yes -- handler logic is application-specific. |
| Error Guard | Catches handler failures so one handler does not crash the pipeline. Calls the handler's error callback. | Yes -- every multi-handler consumer needs this. |
Canonical notification contract
The Drasi MCP Reaction emits notifications/resources/updated notifications. The notification params include fields that are not part of the standard MCP 2025-03-26 spec, any consumer implementation must extract them explicitly:
{
"uri": "drasi://query/{queryId}",
"operation": "added",
"data": { ... }
}| Field | Source | Required? | Meaning |
|---|---|---|---|
uri |
Standard MCP param | Yes | Identifies the query resource that changed. Format: drasi://query/{queryId}. |
operation |
Drasi-specific extension (params.model_extra) |
Yes | The type of change: added, updated, or deleted. |
data |
Drasi-specific extension (params.model_extra) |
Yes | The change payload. Contains the ContinuousQuery result contract fields for the affected row(s). Shape is defined by the query author. |
Consumer handling requirements:
- The MCP
ResourceUpdatedNotification.paramsmust be inspected for model-extensible fields. A strict MCP client that rejects unknown params will not receive Drasi notifications. - The
operationstring maps to three handler methods:on_result_added,on_result_updated,on_result_deleted. - The
datadict must be validated against the expected result-contract schema per query. Do not trust that the shape matches, downstream consumers (especially AI agents receiving untrusted input) must validate before acting.
Connection lifecycle
The MCP connection follows a strict lifecycle that is identical across frameworks:
+---------+
| START |
+----+----+
|
v
+-------------------+
| 1. Resolve URL | Discover the Drasi MCP Reaction endpoint URL
+-------------------+ (from config, service discovery, or environment)
|
v
+-------------------+
| 2. Open transport | Establish Streamable HTTP or SSE connection
+-------------------+ to the MCP endpoint
|
v
+-------------------+
| 3. Initialize | Send MCP `initialize` request.
+-------------------+ Verify server capabilities (resources/list, resources/subscribe).
|
v
+-------------------+
| 4. Discover | (Optional) Call resources/list to enumerate
| queries | available drasi://query/{id} resources
+-------------------+
|
v
+-------------------+
| 5. Subscribe | Call resources/subscribe for each query
| to queries | the consumer needs to track
+-------------------+
|
v
+-------------------+
| 6. Receive | Process incoming notifications/resources/updated
| notifications | and route to handlers by change type
+-------------------+
|
v
+-------------------+
| 7. Unsubscribe | Call resources/unsubscribe when no longer
| from queries | interested in a query (cleanup)
+-------------------+
|
v
+-------------------+
| 8. Close session | Close the MCP session and transport cleanly
+-------------------+
|
v
+---------+
| END |
+---------+Critical implementation notes:
- Steps 1-3 and 8 are generic MCP client behaviour, every MCP SDK handles these identically. Implement once, reuse across consumers.
- Step 4 (
resources/list) returnsQueryInfoobjects. Cache this mapping locally to avoid rediscovering on reconnect if the mapping is stable. - Step 5 (
resources/subscribe) is per-resource. Subscription state is ephemeral. Drasi does not persist subscriptions across server restarts. After any disconnection, step 5 must be re-executed. - Step 7 (
resources/unsubscribe) is best-effort. Log errors but do not fail the shutdown sequence if unsubscribe fails, the server will eventually clean up stale subscriptions.
Handler protocol
Every notification handler should expose these four entry points. This is the canonical consumer-facing API, regardless of the underlying framework:
| Method | Called when | Typical action |
|---|---|---|
on_result_added(query_name, added_data) |
A new row matches the ContinuousQuery result contract | Insert into agent memory, enqueue for processing, log the event |
on_result_updated(query_name, updated_data) |
An existing result row changed | Update in-memory cache, flag agent for re-evaluation |
on_result_deleted(query_name, deleted_data) |
A row no longer matches the query | Remove from memory/cache, log departure |
on_notification_error(query_name, error) |
An error occurred processing this query's notification | Log, increment metrics, alert if error rate exceeds threshold |
Handler implementations should be:
- Idempotent when possible, the same change may be delivered more than once under at-most-once semantics.
- Non-blocking for the notification pipeline, if a handler performs expensive work (database writes, agent invocations), it should queue the work and return quickly.
- Isolated from other handlers, one handler's failure must not prevent other handlers from processing the same notification.
Handler isolation and error containment
A multi-handler consumer must enforce two-layer error containment:
Layer 1: Handler processing isolation
--------------------------------------
for each handler in router.handlers:
try:
handler.on_result_added(query_name, data)
except Exception as e:
log.error("Handler %s failed: %s", type(handler).__name__, e)
try:
handler.on_notification_error(query_name, e)
except Exception:
log.error("Handler %s error callback also failed", type(handler).__name__)
# Continue to next handler regardless
Layer 2: Router-level isolation
--------------------------------
try:
NotificationRouter.route(change_notification)
except Exception as e:
# Router-level error -- this is a bug in the router, not a handler
log.critical("Router failed: %s", e)
increment_metric("router.failures")Rules:
- A handler failure never prevents other handlers from receiving the same notification.
- The error callback (
on_notification_error) is also guarded, if it fails, log and continue. - Router-level failures (not handler-level) are the only errors that should propagate or trigger process-level alerts.
- Log the handler name, query name, change type, and error for every failure.
Notification delivery semantics
Drasi MCP Reaction delivery behaviour that every consumer must account for:
| Property | Behaviour | Consumer implication |
|---|---|---|
| Per-query ordering | Notifications for a single query arrive in the order changes occurred. | You can rely on sequential processing per query. |
| Cross-query ordering | No ordering guarantee between different queries. | Do not assume notification from query A arrives before or after query B. |
| Delivery guarantee | At-most-once. Drasi will not retry a failed delivery. | Handle missed notifications gracefully. Do not rely on every change being delivered. |
| Replay | Drasi does not replay historical notifications to late-joining consumers. | On initial subscription, the consumer should call resources/read to get the current state, then apply incremental changes from notifications. |
| Batching | Notifications are delivered one change at a time. High-throughput sources may produce a rapid sequence of individual notifications. | Consider buffering notifications per query before processing, especially for agent-facing consumers where context-window management matters. |
| Payload completeness | The data field contains only the row(s) that changed, scoped to the query's result contract. |
The consumer cannot derive omitted fields or prior state from a single notification. Maintain local state if historical comparison is needed. |
Resilience patterns
Reconnection
Every consumer must handle connection drops. The canonical pattern is exponential backoff with capped maximum delay:
Attempt 1: wait 1s
Attempt 2: wait 2s
Attempt 3: wait 4s
Attempt 4: wait 8s
...
Cap at: 60s (or configured maximum)On reconnect, the consumer must:
- Re-establish the MCP transport and session (steps 1-3 of the connection lifecycle).
- Re-discover queries if the cached mapping may be stale (step 4).
- Re-subscribe to every previously-subscribed query (step 5).
- Call
resources/readon each subscribed query to recover the current state (mitigates missed changes during the gap). - Resume normal notification processing (step 6).
Subscription state persistence
Because Drasi MCP Reaction subscription state is ephemeral, the consumer should persist subscription metadata locally:
Persisted subscription state (JSON):
{
"subscribed_queries": [
{"name": "freezer-temps", "uri": "drasi://query/freezer-temps"},
{"name": "active-orders", "uri": "drasi://query/active-orders"}
],
"last_connected": "2026-06-11T10:30:00Z",
"session_sequence": 14
}On reconnect, the consumer reads this file, re-subscribes, and calls resources/read on each query to catch up.
Backpressure
Notifications can arrive faster than the consumer processes them. Strategies, in order of preference:
| Strategy | Mechanism | Use when |
|---|---|---|
| Buffer | In-memory FIFO queue (bounded size) between the router and processing logic. The router enqueues; a worker dequeues and calls handlers. | Processing is slower than arrival but catches up during lulls. |
| Drop-newest | When the buffer is full, discard the newest notification. The consumer calls resources/read periodically to recover missed state. |
It is acceptable to miss intermediate states. |
| Drop-oldest | When the buffer is full, discard the oldest queued notification. | Recent state is more important than complete history. |
| Throttle skip | Skip processing some fraction of notifications (e.g., every Nth) and rely on periodic resources/read to stay current. |
Notification volume is predictably high and per-change actions are low-value. |
Do not let backpressure propagate from the handler layer to the MCP transport layer unless the consumer framework explicitly supports it. Drasi MCP Reaction does not implement flow control, and blocking the notification thread will stall all subscriptions.
Graceful degradation when Drasi is unavailable
Define a degraded behaviour for each handler when the MCP connection cannot be established or is lost for an extended period:
- Serve stale data from the last known
resources/readresult (with a clear staleness indicator for downstream consumers). - Queue agent requests that depend on Drasi data with a deadline, if the connection is not restored within the deadline, the request fails safe.
- Alert when the connection loss exceeds the recovery budget defined in the project's end-to-end SLOs.
Consumer-side backpressure
Beyond the per-handler strategies above, a production consumer should define system-level backpressure limits:
Consumer backpressure configuration:
buffer_max_size: 1000 # Max notifications in buffer
per_query_max_size: 200 # Max queued per individual query
drop_strategy: "oldest" # What to drop when full
read_catchup_interval: 30s # How often to call resources/read
max_handler_timeout: 5s # Max time a handler can blockThese limits prevent memory exhaustion and ensure the consumer remains responsive even under peak load.
Consumer-side testing
Notification handlers must be testable without a running Drasi instance. The canonical approach is to inject synthetic ChangeNotification objects into the router and assert handler behaviour:
| Test scenario | Synthetic input | Expected handler action |
|---|---|---|
| Row added | ChangeNotification(ADDED, "query-a", {"id": "1", "temp": 35}) |
on_result_added called with correct query_name and data |
| Row updated | ChangeNotification(UPDATED, "query-a", {"id": "1", "temp": 36}) |
on_result_updated called |
| Row deleted | ChangeNotification(DELETED, "query-a", {"id": "1"}) |
on_result_deleted called |
| Handler failure | Handler raises exception during on_result_added |
Error logged, other handlers still receive notification |
| Malformed notification | Incomplete or extra fields in data | Handler validates before acting; invalid data is logged and skipped |
| Multiple handlers | Two handlers registered on one router | Both receive the notification independently |
Test at three levels:
- Unit: Synthetic
ChangeNotification-> single handler -> assert side effect (log line, memory state, queue depth). - Integration: Mock MCP session -> full router + handler pipeline -> verify all handlers fire and errors are contained.
- End-to-end: Running Drasi instance -> MCP Reaction -> consumer -> verify notification reaches handler (see
bundles/validation/guide.md).
Framework-specific implementations
The architecture above is framework-agnostic. The subsections below show how to map it to specific agent frameworks.
Microsoft Agent Framework
[VERIFY] EvidenceType = Docs WhereToCheck = Microsoft Agent Framework SDK documentation for MCP client integration Claim = MAF MCP client session customization for Drasi-specific notification params [/VERIFY]
In Microsoft Agent Framework, the consumer architecture maps as follows:
| Architecture component | MAF equivalent |
|---|---|
| MCP Transport + Session | Microsoft.AgentFramework.McpClient or custom IMcpClient implementation using the ModelContextProtocol SDK |
| Notification Router | Custom middleware that intercepts ResourceUpdatedNotification and dispatches by operation field |
| Handler -> Agent Memory | Agent ConversationState or AgentState -- inject notification content as state updates |
| Handler -> Queue | Channel or BufferBlock for sequential processing of notifications |
| Error Guard | Middleware exception handler with ILogger instrumentation |
Key implementation considerations for MAF:
- The MCP
2025-03-26Streamable HTTP transport is compatible with .NET'sHttpClient. Use the standardIMcpClientwith Streamable HTTP configuration. - Drasi's custom
operationanddatafields inResourceUpdatedNotification.paramsrequire either: (a) a customIMcpProtocolimplementation that parses the extensions, or (b) accessing the raw JSON payload before MCP type deserialization. Prefer (b) for forward compatibility, inject a delegating handler that extracts the fields before the standard MCP parser runs. - Wire the Notification Router as a singleton service registered in the DI container. Handlers are registered as
INotificationHandlerimplementations that MAF resolves per-notification or per-agent-turn. - For agent memory injection, implement a handler that writes to
ConversationStateorAgentStatethrough MAF's state management APIs. The agent reads the state on the next turn and incorporates the change data into its reasoning. - The subscription lifecycle lives in a long-running
BackgroundServiceor agent activation handler. The service connects on start, reconnects with exponential backoff on failure, and gracefully unsubscribes on shutdown. - Backpressure is handled by the agent's turn processing model, notifications are buffered and presented to the agent on its next turn, not injected mid-turn.
Semantic Kernel
In Semantic Kernel, the consumer architecture maps as follows:
| Architecture component | Semantic Kernel equivalent |
|---|---|
| MCP Transport + Session | McpKernel or MCPClient from Microsoft.SemanticKernel.Connectors.Mcp |
| Notification Router | Custom INotificationHandler connector |
| Handler -> Agent Memory | KernelMemory or in-process SemanticTextMemory |
| Handler -> Queue | Channel<T> for buffered processing |
| Error Guard | SK's IFunctionInvocationFilter or try/catch in the connector |
Key considerations:
- SK's MCP connector can be configured with Streamable HTTP transport. Point it at the Drasi MCP Reaction endpoint.
- The Drasi-specific notification params may need a custom
IMcpClientHandleror middleware layer, verify against the current SK MCP connector source. - Use
Pluginregistration to expose notification events as SK functions. EachChangeNotificationcan become aKernelFunctioninvocation.
Custom consumers (Rust, Go, Python)
For custom consumers not using an agent framework:
- Use the language's MCP SDK (or implement Streamable HTTP manually against the
2025-03-26spec). - Build the Notification Router as a small dispatcher, it is usually under 100 lines in any language.
- Implement handlers as functions or lightweight trait implementations.
- For Python, the
langchain-drasilibrary (pip install langchain-drasi) provides a ready-madeNotificationRouter,BaseDrasiNotificationHandler, andMCPClient, you can use these directly without the LangChain tool wrapper. - For Rust, the
drasi-libcrate provides in-process embedded consumption; seebundles/drasi-lib/guide.md. - For Go, implement the notification parsing inline, the Drasi-specific params are simple dictionary lookups after JSON deserialization.
Agent contract
Before wiring Drasi to an agent, define:
- Agent scenario and decision boundary.
- Query resources exposed to the agent.
- Resource URI names.
- Payload schema for added, updated, and deleted operations.
- Data classification and redaction rules.
- Authorization boundary for the MCP endpoint.
- Subscription lifecycle.
- Latency expectations.
- Human review or approval requirements for downstream actions.
- Failure and replay behavior.
MCP security requirements
See bundles/security/guide.md for the broader trust-boundary table; this section captures MCP-specific requirements only.
Do not expose an MCP Reaction endpoint publicly without:
- Authentication.
- TLS.
- Authorization to specific resources or tenants where applicable.
- Origin validation for Streamable HTTP clients where browser or local-network access is possible.
- Binding local development endpoints to localhost where practical.
- Rate limiting or connection controls.
- Audit logging.
- Data minimization in resource payloads.
Use kubectl port-forward only for local development and testing.
MCP transport checks
For Streamable HTTP, verify the endpoint path, supported methods, session-id handling, accepted content types, and SSE fallback behaviour against current MCP and Drasi docs. Do not document an endpoint as production-ready until the client can initialize, subscribe, receive notifications/resources/updated, recover from reconnect, and fail closed when unauthorized.
Note: Drasi MCP Reaction is currently pinned to MCP spec 2025-03-26 (confirmed unchanged 2026-08-16), which is three revisions behind upstream - re-check Drasi MCP Reaction docs before any client implementation.
MCP spec lag callout
Drasi MCP Reaction is currently pinned to MCP spec 2025-03-26, while upstream MCP has since shipped 2025-06-18 (Stable; reclassified MCP servers as OAuth Resource Servers, mandated Resource Indicators per RFC 8707), 2025-11-25 (added OpenID Connect Discovery, icons metadata, incremental scope consent, elicitation), and 2026-07-28 (current Latest Stable; introduced an extensions architecture (Tasks, MCP Apps, Skills over MCP), none of which Drasi exposes). Drasi is therefore three MCP spec revisions behind upstream. Implication: Drasi MCP Reaction does not yet implement RFC-8707 Resource Indicators, OAuth Resource Server discovery, or any 2026-07-28 extension; do not assume those when designing client auth flows or agent capabilities. Cite: https://modelcontextprotocol.io/specification/2025-06-18/basic/transports and https://modelcontextprotocol.io/specification/. Re-check Drasi MCP Reaction docs before any client implementation.
Practical consequences of the spec gap:
- Do not configure Drasi MCP clients with Resource Indicator assumptions unless a pinned Drasi release explicitly supports them.
- Do not rely on OpenID Connect Discovery metadata being present at the Drasi MCP endpoint.
- Do not design incremental scope-consent flows around Drasi MCP Reaction until the target release documents support.
- Put any required OAuth/OIDC enforcement in the gateway or reverse proxy in front of Drasi, then map the authenticated principal to allowed
drasi://query/...resources. - Record the MCP spec revision in
templates/drasi-version-pinning.mdand in the architecture decision record.
Verification commands before client implementation:
drasi list reaction -n "$DRASI_NAMESPACE"
drasi describe reaction "$REACTION_NAME" -n "$DRASI_NAMESPACE"
curl -fsS "$DRASI_MCP_BASE_URL"/.well-known/oauth-protected-resource || trueIf the discovery document is absent, treat OAuth Resource Server discovery as unsupported for that deployment and enforce auth at the gateway.
Emerging: MCP Events extension (direction of travel)
The MCP Triggers & Events working group is defining an MCP Events extension (event/webhook-driven MCP), and a core Drasi contributor maintains an independent Rust implementation that bridges Drasi continuous-query changes into it ; see github.com/amansinghoriginal/drasi-mcp-events (CQ bridge) and .../drasi-mcp-agent (Dapr actor, scale-to-zero, wakes on change). Status as of 2026-08-24: draft-spec implementation in a contributor's personal repo, not shipped in any Drasi release. Treat as a design signal only:
- Do not build production agent integrations against the draft extension; use the pinned Drasi MCP Reaction surface above.
- When designing long-lived agents around Drasi change feeds today, webhook/queue delivery via HTTP or PostDaprPubSub reactions remains the supported path.
- Re-check when a Drasi release or drasi.io docs announce Events-extension support; at that point this section graduates from signal to guidance.
Payload design
Agent payloads should be:
- Small.
- Stable.
- Strongly typed where possible.
- Free of secrets.
- Free of unnecessary PII.
- Descriptive enough for the agent to explain why a state changed.
- Backed by source identifiers so the agent can cite or fetch authoritative detail if needed.
Microsoft agent and Fabric-oriented projects
For new Microsoft-oriented agent projects, treat Drasi as the change-detection and eventing layer, not as the system of record.
Recommended boundary:
- Drasi detects and packages changes.
- MCP Reaction or event reactions publish query-result changes.
- Agent frameworks consume the event/resource contract.
- Fabric, Foundry, or application services remain responsible for durable analytics, long-running orchestration, and governed business actions.
Do not let the agent mutate production systems directly from a Drasi event unless the project has an explicit approval, authorization, idempotency, and audit design.
For a worked example of this pattern, see apps/rag-chat-app in the drasi-project/learning repository. It uses Drasi as the change-detection layer feeding an AI RAG pipeline: Drasi detects document/message updates, and the chat application consumes those changes through its own integration layer, not by letting the agent call back into Drasi.
Validation
Agent integration is complete only when:
- MCP or reaction endpoint is reachable through the intended secure path.
- Client can subscribe to the resource.
- Added, updated, and deleted notifications are observed.
- Payload contains the agreed fields.
- Agent behavior is tested with expected and unexpected events.
- The failure mode is safe.
MCP transport and Drasi version compatibility
The MCP Reaction is a recent addition to the Drasi platform; transport and security expectations have been moving targets. Pin both sides explicitly.
Transport facts to re-verify per release
- The MCP specification at
2025-03-26adopts Streamable HTTP with SSE as a fallback for backwards compatibility with2024-11-05clients. Re-check the current MCP spec before assuming a transport. - Drasi MCP Reaction was introduced as experimental in early
drasi-platform0.8 releases and refined in 0.9.x. Treat it as preview surface until release notes explicitly mark it stable. - Confirm whether your pinned Drasi platform version emits
notifications/resources/updatedfordrasi://query/<id>or a different resource URI scheme - release notes have iterated on the URI shape.
Production hardening (verify in your pinned version)
| Concern | What to enforce | Where |
|---|---|---|
| Origin validation | Reject requests with disallowed Origin headers |
Front the MCP endpoint with APIM / reverse proxy if Drasi does not enforce it natively. |
| Rate limiting | Per-token quotas; per-tenant ceilings | APIM / reverse proxy. |
| Authentication | OIDC bearer or workload identity tokens; no shared keys | APIM / reverse proxy + Drasi reaction config. |
| Resource scoping | Each MCP token sees only its own subset of drasi://query/... resources |
Map tokens -> allowed query IDs in the gateway. |
| Local-only servers | Bind to 127.0.0.1, not 0.0.0.0 |
Drasi Server host: setting or container runtime args. |
Compatibility check before pinning
- Confirm your Drasi platform version's release notes mention MCP Reaction.
- Confirm your MCP client supports Streamable HTTP (or accepts the SSE fallback the spec defines).
- Run the validation harness in
bundles/validation/guide.mdwith a small synthetic query and verify anotifications/resources/updatedevent reaches the client. - Record the Drasi platform version, MCP spec version, and client version in the architecture decision record.
Untrusted content (prompt-injection defence)
Source rows are not trusted instructions. They are data produced by upstream systems whose contents may be partially or fully attacker-controlled (customer-submitted strings, public form input, scraped fields, user-editable display names). When those rows flow through drasi://query/{queryId} into an agent's context window, every string field is a potential prompt-injection vector.
Rules
- Tag source-derived strings as untrusted at the boundary. The MCP Reaction (or the gateway in front of it) should mark each source-derived field with a provenance tag - for example, wrapping each field in a structured envelope
{value: "...", trust: "source-derived", source_id: "..."}. Downstream agents read the tag, not the bare string. - Consuming agent must treat tagged strings as data, not instructions. No tool calls that take raw source text as a control argument (e.g., do not pass a source-derived string into a shell, into an LLM system prompt without delimiters, or into a tool that interprets natural-language commands) without a provenance check and an allow-listed shape.
- ContinuousQuery authors redact or quote adversarial fields. Fields known to carry adversarial content (free-text customer input, descriptions, comments) MUST be either redacted, hashed, truncated, or wrapped in quoting that prevents the agent from interpreting them as instructions. Prefer projecting only the IDs the agent needs to fetch authoritative detail itself through a trusted path.
- No agent action solely on the basis of source text. Downstream actions taken by an agent must be conditioned on structured fields (IDs, enums, numeric thresholds) - never on free-text content from a source row. Free-text fields are for human-facing display or for the agent to explain why a state changed, not for the agent to act on.
- Log the provenance. When the agent takes an action, the audit record should capture which source row triggered it and which fields were used so a reviewer can trace back to the originating source data.
This is the same threat as classic prompt injection in retrieval systems: anything flowing in from the world is suspect, and a Drasi result is just a fast-path retrieval channel. The Drasi side of the contract is responsible for tagging and shaping; the agent side is responsible for treating the tag honestly.