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

# Context Providers

> Pre-fetch external context and inject it into the agent's system prompt

# Context Providers

## Why

A common agent shape: "answer using these N markdown docs / this DB row / today's status feed".

You can do that with tools (the LLM calls `fetch_status()` whenever it needs the data), but for context that the LLM *always* needs, tools are wasteful:

* An extra round-trip per turn.
* The LLM might forget to call.
* Tool definitions take prompt space.

The alternative: **pre-fetch the context** and inject it into the system prompt before the model sees the user's message. That's what a `ContextProvider` does.

## `ContextProvider` interface

```typescript theme={null}
interface ContextProvider {
  /** Stable name used in <context name="..."> wrapping. */
  readonly name: string;
  /**
   * Return the context string to inject, or null if no context is available for this query.
   * The query is the user's latest input; ctx is the standard RunContext (userId, sessionState, etc.).
   */
  fetch(query: string, ctx: RunContext): Promise<string | null>;
}
```

The framework wraps your return value as:

```
<context name="status-feed">
... your text here ...
</context>
```

Tags help the model distinguish multiple sources.

## Built-in providers

### `FilesystemContextProvider`

```typescript theme={null}
import { FilesystemContextProvider } from "@agentium/core";

const notes = new FilesystemContextProvider({
  basePath: "./agent-notes",
  glob: "*.md",                // default "**/*.md"
  encoding: "utf8",            // default "utf8"
  maxFileSize: 100 * 1024,     // default 100KB per file
  maxFiles: 20,                // default 20 files combined
});
```

Reads every file matching `glob` under `basePath`, concatenates them, returns the result. Files over `maxFileSize` are skipped. Total file count capped at `maxFiles`.

Each file is prefixed with `# <relative path>` so the model knows what came from where.

**Use for:** project README, design docs, prompt templates loaded from disk, user-uploaded notes.

### `HttpContextProvider`

```typescript theme={null}
import { HttpContextProvider } from "@agentium/core";

const status = new HttpContextProvider({
  url: "https://status.example.com/api/current",
  method: "GET",                                       // default "GET"
  headers: { Authorization: `Bearer ${process.env.STATUS_TOKEN}` },
  body: undefined,                                     // optional, for POST
  timeoutMs: 5_000,                                    // default 5s
  cacheTtlMs: 60_000,                                  // default 0 (no cache)
});
```

Fetches the URL on every `fetch()` call unless `cacheTtlMs > 0`, in which case the result is cached for that many milliseconds.

If the request fails (network error, non-2xx), the provider returns `null` and silently continues. No retries (wrap with your own if you need them).

**Use for:** status feeds, configuration endpoints, "latest pricing" data.

### `DatabaseContextProvider`

```typescript theme={null}
import { DatabaseContextProvider } from "@agentium/core";

const profile = new DatabaseContextProvider({
  name: "user-profile",
  fetch: async (ctx) => {
    const row = await db.users.findOne({ id: ctx.userId });
    if (!row) return null;
    return `name: ${row.name}\nplan: ${row.plan}\nsignup: ${row.signupAt}`;
  },
});
```

Thin wrapper that just adapts an arbitrary async function to the `ContextProvider` interface, with the `name` used for the `<context>` tag.

**Use for:** anything that's not a file or HTTP endpoint — DBs, cache, in-process Maps, custom APIs.

## Plugging into an Agent

```typescript theme={null}
import { Agent, openai, resolveContextProviders } from "@agentium/core";

const providers = [notes, status, profile];

const agent = new Agent({
  name: "support-bot",
  model: openai("gpt-4o"),
  instructions: ({ context }) => `
You are a support assistant. Use the context below.

${context}

Be concise.`,
});

app.post("/chat", async (req, res) => {
  const ctx = { userId: req.user.id }; // any RunContext-shaped object
  const context = await resolveContextProviders(providers, req.body.input, ctx);
  const result = await agent.run(req.body.input, {
    context,            // merged context string
    userId: req.user.id,
  });
  res.json(result);
});
```

(Direct integration into the Agent's `instructions` resolver is on the roadmap; for now, fetch the context and inject it yourself.)

## `resolveContextProviders(providers, query, ctx)`

The helper that runs all providers in parallel and concatenates their outputs.

```typescript theme={null}
async function resolveContextProviders(
  providers: ContextProvider[],
  query: string,
  ctx: RunContext,
): Promise<string>;
```

* Runs `fetch()` on every provider in parallel via `Promise.allSettled`.
* Skips providers that throw or return `null`.
* Wraps each non-null result in `<context name="...">...</context>`.
* Returns the joined string (or `""` if nothing returned).

If you need different orchestration (sequential, with-deadline, cancellable), drive the providers yourself.

## Caching strategies

`HttpContextProvider` has built-in TTL caching. For the others, layer it yourself:

```typescript theme={null}
class CachedContextProvider implements ContextProvider {
  private cache?: { value: string | null; expires: number };

  constructor(
    private inner: ContextProvider,
    private ttlMs: number,
  ) {}

  get name() { return this.inner.name; }

  async fetch(query: string, ctx: RunContext) {
    const now = Date.now();
    if (this.cache && now < this.cache.expires) return this.cache.value;
    const value = await this.inner.fetch(query, ctx);
    this.cache = { value, expires: now + this.ttlMs };
    return value;
  }
}
```

Or just memoize at the orchestrator level.

## Per-user context

`ctx` carries `userId`, `tenantId`, `sessionState`. Use them inside `fetch()` to personalize:

```typescript theme={null}
new DatabaseContextProvider({
  name: "preferences",
  fetch: async (ctx) => {
    const prefs = await db.preferences.findOne({ userId: ctx.userId });
    return prefs ? JSON.stringify(prefs) : null;
  },
});
```

The provider gets the same `RunContext` that the agent sees, so any state you've stashed via `RunContext.sessionState` is available.

## Token cost considerations

Context is paid for on every turn. A 50KB markdown corpus injected into the system prompt costs \~\$0.03 per turn at GPT-4o pricing.

* **Always-on, small:** context provider is the right choice.
* **Sometimes-on, large:** use a tool (`searchNotes`) or RAG retrieval instead.
* **Always-on, large:** consider prompt caching (Anthropic's `cache_control`, OpenAI's `prompt_caching`) — Agentium honors both when the underlying provider supports them.

## See also

* [Prompts and Instructions](/agents/prompts) — how the resolved context gets composed into the system message
* [GraphRAG](/knowledge/graphrag) — when "context" is a graph query
* [Skills](/skills/overview) — the skill system is itself a kind of context provider (with extra structure)
