Connic Run Context is shared storage for an agent run. Applications can define their own values for middleware, tools, and hooks to use throughout the run, without the AI needing to know those values or supply them as arguments. The application decides what the context holds and which values belong in the prompt.
Use application data throughout the run
The Run Context is a Python dictionary with custom fields defined by the application. It can hold text, numbers, lists, and nested objects.
At the start of a run, the incoming data is available under context["payload"]. A middleware function, which runs Python code before or after agent processing, can extract values from that data and add information from the application. Tools use the same context during the run, so a value supplied once can be used across several steps.
Enabled input guardrails process the incoming data before middleware. Any redactions they make are reflected in context["payload"].
Context can also hold intermediate results. A tool might record which database entry it processed, so a later tool or the middleware after processing can use it. The same context is available to tool hooks, which run custom code before or after a tool call.
Supply application data for a billing assistant
In this billing example, the application defines three fields: customer_id, locale, and subscription_tier. Its backend sends them alongside a question to a Connic webhook connected to the LLM agent billing-assistant. The customer ID and subscription tier come from the customer account in the example application.
{
"customer_id": "cus_demo_42",
"locale": "en-GB",
"subscription_tier": "business",
"question": "Has invoice INV-1042 been paid?"
}The invoices database collection contains a corresponding example invoice with invoice_id: "INV-1042", customer_id: "cus_demo_42", and status: "paid". The tool can then look up the payment status to answer the question.
Middleware prepares the context and message
The file middleware/billing-assistant.py belongs to the billing-assistant agent. Connic associates middleware with an agent through this filename and calls before() before agent processing. The function copies the three values from context["payload"] into custom context fields and passes only the question as the user message.
from typing import Any
from connic import StopProcessing
async def before(
content: dict[str, Any], context: dict[str, Any]
) -> dict[str, Any]:
payload = context["payload"]
required = ("customer_id", "locale", "subscription_tier", "question")
if not isinstance(payload, dict) or any(
not isinstance(payload.get(key), str) or not payload[key].strip()
for key in required
):
raise StopProcessing("Missing or invalid support request")
facts: dict[str, str] = {
"customer_id": payload["customer_id"],
"locale": payload["locale"],
"subscription_tier": payload["subscription_tier"],
}
context.update(facts)
return {"role": "user", "parts": [{"text": payload["question"]}]}
async def after(response: str, context: dict[str, Any]) -> str:
context["invoice_lookup_attempted"] = "last_invoice_id" in context
return responseThe initial check expects a nonempty string for each of the four fields. If a required value is missing, StopProcessing ends the run before the model call. After validation succeeds, context.update(facts) adds the values to the shared dictionary. The returned message contains the question for the AI to answer; the customer ID, locale, and subscription tier remain available through context for the rest of the run.
This example processes a text question. For messages with attachments, middleware can pass the corresponding file parts along with the text. The middleware message formats describe how to assemble these contents.
After the response, Connic calls after() with the same context. The function checks for last_invoice_id, which the tool sets, and records whether an invoice lookup was attempted under invoice_lookup_attempted. It returns the response unchanged.
A tool uses data the AI does not have to supply
To look up the invoice, get_invoice_status receives the invoice reference from the AI and reads the customer ID from context. The function uses the parameter context: dict[str, Any] for this:
from typing import Any
from connic.tools import db_find
async def get_invoice_status(
invoice_id: str, context: dict[str, Any]
) -> dict[str, Any]:
"""Look up payment status for an invoice in the current account.
Args:
invoice_id: Invoice reference from the billing question.
"""
context["last_invoice_id"] = invoice_id
result = await db_find(
"invoices",
filter={
"invoice_id": invoice_id,
"customer_id": context["customer_id"],
},
fields=["invoice_id", "status"],
limit=1,
)
if "error" in result:
return {"error": "Invoice lookup unavailable"}
if not result["documents"]:
return {"error": "Invoice not found"}
invoice = result["documents"][0]
return {"invoice_id": invoice["invoice_id"], "status": invoice["status"]}Connic supplies the context parameter automatically when the tool runs; the model does not see it as an argument to fill in. The AI supplies invoice_id, and the tool code adds a customer filter using context["customer_id"].
The tool reads the invoices collection with db_find, matching both the invoice reference and customer ID. A match returns the invoice reference and payment status. A failed database query returns Invoice lookup unavailable; a query with no match returns Invoice not found. The tool also records the invoice reference under last_invoice_id in context, making it available to other tools and middleware later in the run.
Use selected values to guide the response
Some context values should also influence the response. In the billing example, the application uses the prompt to tell the model which language and subscription tier to consider. The field names appear as placeholders in the system prompt. This excerpt from agents/billing-assistant.yaml connects the prompt to the tool and grants access to the invoices collection. The agent name, version, type, and model are defined in the rest of the agent configuration.
system_prompt: |
Answer billing questions in locale {locale}.
The customer's subscription tier is {subscription_tier}.
Use get_invoice_status to check invoice payment status.
Report lookup errors without guessing a payment status.
tools:
- billing.get_invoice_status
database:
collections:
invoices:
prevent_write: true
prevent_delete: trueBefore calling the model, Connic replaces {locale} with en-GB and {subscription_tier} with business. The tool reads the customer ID from context for its customer filter. The application decides which context values to include in the prompt.
The first two instructions then read “Answer billing questions in locale en-GB.” and “The customer's subscription tier is business.” The entry under tools makes billing.get_invoice_status available to the agent. The database configuration allows reads from invoices and blocks writes and deletes for that collection.
Follow context through the run
With an example invoice stored as paid, a call to get_invoice_status follows this sequence:
| Step | Data in the example |
|---|---|
| Incoming data and middleware | The payload values are stored in context. The model receives the message “Has invoice INV-1042 been paid?” |
| Prompt | The system prompt contains the substituted values en-GB and business. |
| Tool call | The AI supplies {"invoice_id":"INV-1042"}. Connic passes context to the Python function. |
| Lookup and result | The tool uses cus_demo_42 for the customer filter, adds last_invoice_id to context, and returns {"invoice_id":"INV-1042","status":"paid"} to the AI. |
| After middleware | After the response, after() sets invoice_lookup_attempted to true. Connic saves the updated context with the run. |
Connic saves the final context with the completed run. The custom fields in this example look like this:
{
"customer_id": "cus_demo_42",
"locale": "en-GB",
"subscription_tier": "business",
"last_invoice_id": "INV-1042",
"invoice_lookup_attempted": true
}The Context section in the run details shows the saved values. Alongside the messages and tool calls in the trace, these values show what the application supplied, what the AI received, and what was added during processing. The run-details API exposes these values in the run_context field.
Alongside the custom fields, context contains the incoming data under payload and run information such as run_id, agent_name, connector_id, and timestamp. Before calling after(), Connic also adds token usage under token_usage and the elapsed duration under duration_ms. Middleware can use this information together with the results from tools.
How changes carry through and beyond a run
Middleware, tools, and tool hooks read and update the same dictionary. When a tool writes a value, later calls can use it. Tool hooks can also change parameters before a query or results afterward, using the values already in context.
Parallel tools have no fixed order for writes to context. If one step needs another's result, the calls need to run in that sequence. Connic saves context as JSON, so custom field values need to be JSON-compatible.
For the system prompt, Connic uses the values available before the model call. When a later tool result should influence the current response, the tool returns the relevant data to the model. In this example, the AI receives the payment status as a tool result, while the invoice reference also remains in context for the after middleware.
Each new run receives its own context; saved context records the values from the completed execution. For continuing conversations, Connic also retains the conversation history and values previously copied for prompts. Current context values replace entries with matching names. Entries absent from the new run can remain from the earlier conversation. Middleware can set the values needed by the prompt on every run, for example to apply a changed language preference.
If a value is also absent there, a simple placeholder such as {locale} remains as text in the prompt. Missing values in nested lookups or format specifications can cause formatting errors. In this example, middleware checks the locale beforehand and stops the run if it is missing. For optional fields, it can set a suitable default instead.
Share your own values with tools and middleware through Run Context, and choose which values your AI receives to help it answer.
Start building with Connic