Build Local Agents

Configure durable standalone agents with scoped tools, approvals, history, and recovery.

Context Harness can run standalone agents directly in a project. These agents use a configured model, select a small set of local tools, and record their runs in the project’s SQLite database. They are different from MCP profiles: a standalone agent owns a bounded model/tool loop and can be inspected or resumed locally, while a profile only prepares a conversation owned by another client. See Profiles Overview for a direct comparison.

This guide builds a generic project researcher. The same structure works for code review, release preparation, incident analysis, migration planning, and other workflows without assuming that your context lives in a wiki.

You can follow the configuration manually below or have Codex apply the same workflow with the Agent Setup Skill. The skill is useful when adapting an existing project; the manual path is useful when you want to understand or author every resource yourself.

Configuration map

Local-agent configuration has three layers:

LayerLocationResponsibility
Runtime config.ctx/config.tomlDatabase, model aliases, connectors, retrieval, and server settings
Agent resource.ctx/agents/<name>.tomlRole, selected model and tools, execution bounds, and permissions
Tool binding.ctx/tools/<name>.tomlPublic tool name mapped to a trusted implementation with narrower fixed values and restrictions

The names connect the layers. An agent’s model = "default" selects [models.default]; tools = ["project.search"] selects a binding whose [tool].name is project.search; and a binding restricted to filesystem:project refers to [connectors.filesystem.project].

What you will create

.ctx/
├── config.toml
├── agents/
│   └── project-researcher.toml
└── tools/
    ├── project-search.toml
    └── project-get.toml

The model sees project.search and project.get. Both are declarative aliases over trusted built-in implementations and are restricted to one connector source. The declarations narrow authority; they cannot load executable code or grant new capabilities.

1. Configure the model and source

Create .ctx/config.toml:

[db]
path = ".ctx/data/ctx.sqlite"

[chunking]
max_tokens = 700
overlap_tokens = 80

[embedding]
provider = "disabled"

[retrieval]
final_limit = 12
hybrid_alpha = 0.6
candidate_k_keyword = 80
candidate_k_vector = 80
group_by = "document"
doc_agg = "max"
max_chunks_per_doc = 3

[server]
bind = "127.0.0.1:7331"

[models.default]
provider = "openai"
model = "gpt-5-mini" # replace with a model available to your project
api_key_env = "OPENAI_API_KEY"

[connectors.filesystem.project]
root = "."
include_globs = ["**/*.md", "**/*.txt", "**/*.rs", "**/*.py", "**/*.ts", "**/*.tsx"]
exclude_globs = ["**/.git/**", "**/.ctx/**", "**/target/**", "**/node_modules/**"]
follow_symlinks = false

The model alias stores only the environment-variable name. Export the actual credential in the process that runs ctx:

export OPENAI_API_KEY="..."

The local runtime currently includes an OpenAI Responses adapter. The runtime interface itself is provider-neutral, so embedding hosts can register other model providers in code.

2. Bind source-scoped retrieval tools

Create .ctx/tools/project-search.toml:

schema_version = 1

[tool]
name = "project.search"
implementation = "builtin.retrieval.search"
description = "Search indexed project files"

[config]
source = "filesystem:project"

[fixed]
limit = 8

[restrictions]
sources = ["filesystem:project"]
max_output_bytes = 65536

Create .ctx/tools/project-get.toml:

schema_version = 1

[tool]
name = "project.get"
implementation = "builtin.retrieval.get"
description = "Read one indexed project document"

[config]
source = "filesystem:project"

[restrictions]
sources = ["filesystem:project"]
max_output_bytes = 131072

The fixed search limit is removed from the model-visible schema and cannot be overridden by a tool call. The source restriction is enforced again at runtime, including when a document is fetched by ID.

You can also bind builtin.scoped_file_read to a concrete workspace-relative directory. It rejects absolute paths, parent traversal, special files, and symlink escapes. See the repository’s examples/local-agents directory for a docs.read example.

3. Define the agent

Create .ctx/agents/project-researcher.toml:

[agent]
name = "project-researcher"
description = "Answers project questions from indexed evidence"
model = "default"
tools = ["project.search", "project.get"]

[agent.execution]
max_turns = 8
timeout_seconds = 180

[agent.permissions]
mode = "read-only"

[prompt]
system = """
Search project context before answering factual questions. Use project.get when a
search excerpt is not enough. Cite file paths or document titles for factual
claims. If the indexed evidence is incomplete, identify the missing evidence
instead of guessing.
"""

Keep the reusable role and evidence policy in prompt.system. Put the immediate job in the input to ctx agent run. This keeps one agent useful across many projects and tasks.

4. Validate without side effects

Run validation before creating a database or contacting a provider:

ctx agent validate
ctx tool bindings validate
ctx agent show project-researcher --json
ctx tool bindings show project.search --json

Agent and binding inspection is static. It does not read credentials, start MCP servers, evaluate privileged Lua modules, open the database, call a model, or access the network. Secret references appear as references, never values.

With --config or CTX_CONFIG, Context Harness loads only the agents and tools directories beside that config file. Without an explicit config, it layers global resources below .ctx/agents and .ctx/tools. Overrides replace a whole resource and require override = true; fields never merge implicitly.

5. Initialize, sync, and run

ctx init
ctx sync filesystem:project
ctx search "architecture" --source "filesystem:project"
ctx agent run project-researcher \
  "Explain the architecture and cite the files you used."

If the direct search is empty, fix the connector or sync state before debugging the agent. Missing context is not evidence that no relevant documents exist.

Interactive runs may request approval for capabilities allowed by the agent but requiring approval under host policy. --non-interactive denies those requests; it does not auto-approve them.

6. Inspect and recover runs

ctx agent history --limit 20
ctx agent inspect <run-id>
ctx agent resume <run-id>

History records model usage, tool calls, approval decisions, binding identities, and terminal state without storing credential values or provider response bodies. Resume rejects a changed agent, model binding, tool schema, fixed configuration, restriction, or uncertain side effect. MCP-backed runs are non-resumable because their external session cannot be reconstructed safely.

Conversation history and memory

Conversation state is retained within one run. Each model response, tool call, and tool result is carried into the next turn. Safe checkpoints preserve those messages so an interrupted run can continue with ctx agent resume <run-id>.

A new ctx agent run starts a new conversation containing only the configured system prompt and the new input. Previous runs are persisted for audit and inspection, but they are not automatically placed in the new model request and are not currently exposed as searchable agent memory. Profiles retain no conversation state at all; their external client owns it.

To carry knowledge between runs today, persist the durable result in a configured connector source and sync it into Context Harness. Automatic cross-run memory should be treated as a separate, opt-in feature with explicit retention, scope, provenance, and deletion controls—not as an implicit replay of every transcript.

Permissions and approvals

Read-only mode is the safest default:

[agent.permissions]
mode = "read-only"

For a workflow that genuinely needs more authority, replace named mode with explicit lists:

[agent.permissions]
allow = ["read_only"]
require_approval = ["workspace_write", "process_execute"]

Capabilities absent from both lists are denied. Effective authority is the intersection of host grants, implementation requirements, binding restrictions, agent permissions, and runtime policy. A resource can narrow that intersection; it cannot widen it.

Compiled, Lua, and MCP tools

Declarative bindings separate a public tool contract from trusted executable behavior:

All selected tools still pass through the same validation, policy, approval, cancellation, output-bound, history, and recovery lifecycle.

Use the setup skill

The repository includes the reusable context-harness-agents Codex skill. Once installed, invoke it explicitly:

Use $context-harness-agents to set up a read-only project researcher for this repository.

For an empty workspace, the skill includes a non-destructive initializer that creates the config, agent, and source-scoped bindings. In an existing workspace, it inspects and minimally updates the current resources instead of overwriting them. The dedicated Agent Setup Skill guide covers repository-scoped and personal installation, prompt examples, every generated file, direct initializer use, validation, and the expected completion report.

More examples

The repository example includes:

See examples/local-agents or continue with the CLI reference.