keel
Context

State and tool views

Choose which stored views and executable tools enter the model’s context.

An agent declares both what it can read and which tools it can use. Context assembly turns those declarations into state text and an available tool list.

Render the state you need

Add a read grant to your agent configuration:

reads: [
  {
    block: profileBlock,
    reader: "assistant",
    budgetTokens: readTokens.profile,
  },
],

Here, profileBlock is imported from the state definition and readTokens.profile is a limit in budgets.ts. Its reader can render:

User prefers concise answers.

That text enters render.profile.assistant — section ids follow render.<block>.<reader>, so two grants on the same block through different readers remain distinguishable. The model receives the selected view, not automatic access to the block's raw object. See Reading state for the complete example, reader selection, and update visibility.

Filter tools against the current request

Before adding a tool to context, Keel evaluates its access rule against the turn's ambient context. An allowed tool contributes a description section; the adapter also receives its input schema as a tool specification.

An access rule is either "public" or a named predicate over the ambient context: { id, allows }. The predicate returns true to allow, false to refuse with a generic reason, or a string naming the specific refusal reason. This tool requires a workspace:

// src/shared/tools/workspace-search.tool.ts
import { z } from "zod";
import { defineTool } from "@keel-dev/core";
import type { AppScope } from "@app/ambient.ts";

export const workspaceSearchTool = defineTool<AppScope>({
  id: "workspace-search",
  description: "Search the team workspace",
  access: {
    id: "workspace-members",
    allows: (a) => Boolean(a.scope.workspaceId) || "requires a workspace",
  },
  input: z.object({ query: z.string().default("") }),
  execute: (input, ambient, ctx) => {
    ctx.progress({ step: "scanning workspace" });
    return { result: { hits: [] } };
  },
});

execute receives the validated input, the turn's ambient context, and a context object with signal (cooperative cancellation) and progress (the liveness heartbeat). It returns { result }, optionally with a commit for a state byproduct — a refused commit is appended to the tool's result so the model reacts to it. See Tools for the full execution contract.

Ambient scopeContext assemblyExecution
workspaceId is setThe tool can be included, subject to its other rules.The executor evaluates the same predicate again on a requested call.
workspaceId is nullThe tool is filtered out with the reason requires a workspace.A model-generated attempt is refused by the executor.

The predicate is a pure function of the ambient context — evaluate it over your app's ambient fixtures to print an access matrix. Hiding a tool does not make it impossible for a model to invent its name. That is why the executor performs the second check.

Separate descriptions from schemas

A tool.<id> context section contains the tool's name and description. Its input JSON schema is passed separately in the adapter's tool list. The displayed text-section size therefore does not represent all possible provider overhead from tool definitions.

An active deliverable contract can add a submission tool even though it was not listed among the agent's ordinary tools.

Keep views intentional

Use smaller reader projections when an agent only needs a count or summary. Tool results are another source of model-visible information, so review their contents too. Limiting a reader does not prevent a tool from returning the same underlying data.

State views are assembled at the start of the turn. If a tool updates a block, the opening view remains as rendered; the tool result enters the transcript, and a later turn can read the new value.

Inspect in Studio

Run one turn with the ambient scope a tool requires, then edit the scope in Studio's sidebar (for example, set workspaceId to null) and run another. Compare CONTEXT and the filtered tool events. Check that the profile render still contains only the intended reader output. For a deterministic access test, use a scripted adapter that attempts a filtered tool and verify the refusal.

Next: History and transcripts.