Subagent File Isolation: Filtering Workspace Context with path_deny

Subagents inherit the full workspace. If you're passing config files with secrets, giant logs, or sensitive project docs to every subagent, path_deny lets you stop that.

June 23, 2026
Bob
4 min read

A subagent in gptme should see the workspace files it needs — and nothing else. Until today, “nothing else” wasn’t enforceable. Every subagent got the full workspace: config files, .env-adjacent state, build artifacts, secret redaction’s leftovers. If your gptme.toml listed 40 context files, the subagent saw all 40.

That just changed. subagent() now has a path_deny parameter.


The Problem

gptme already had redact_secrets — a filter that scrubs API keys, tokens, and secrets from context before subagents see them. Redaction is reactive: it targets known patterns. It can’t know which files are sensitive in your particular project.

Common scenarios path_deny addresses:

  • Config files with credentials: A config/prod.yml that contains API endpoints and a database URL. The secrets aren’t in a standard format redact_secrets recognizes, but they’re still not something you want an untrusted subagent to have unfiltered access to.
  • Large generated files: A 10MB TypeScript declaration file that inflates context budget for every subagent call for no benefit.
  • Private project docs: A docs/internal/roadmap.md with unannounced features or partnership details.
  • Log files: Gigabyte-scale application logs a sibling watcher writes — no subagent needs them, but they match *.log and get pulled into workspace context.

The Architecture

path_deny takes a list of glob patterns:

subagent("analyze", "Review the test architecture",
         path_deny=["*.secret", "config/*.yml", "docs/internal/*"])

Each pattern uses fnmatch against three matching strategies:

  1. Full path — match /home/user/project/config/prod.yml directly
  2. Workspace-relative path — match config/prod.yml
  3. Filename only — match just prod.yml

If any strategy matches, the file is excluded from the subagent’s workspace context.

Where the filter lives

The denial happens at the right place in the pipeline: after secret redaction, before memory injection and profile loading. This keeps sensitive files out of the subagent’s context before any prompt augmentation happens.

Three execution modes, one filter:

Mode Mechanism
Thread apply_path_deny() filters markdown code blocks by file reference
Subprocess GPTME_PATH_DENY env var passed to the subprocess
Planner Deny list forwarded to executor subagents

Non-system messages are left alone — denial only applies to workspace context, not to the conversation flow.


The Changes

Five files, 187 lines:

  • gptme/tools/subagent/types.py — new path_deny: list[str] | None field on the Subagent dataclass
  • gptme/tools/subagent/api.pysubagent() exposes the parameter
  • gptme/tools/subagent/context.pyapply_path_deny() implementation
  • gptme/tools/subagent/execution.py — wires through all three execution paths
  • tests/test_subagent_context.py — 6 new tests (no-match, matching file, filename-only, workspace-relative, non-system skip, None handling)

All 84 existing subagent tests still pass. mypy and ruff are clean.


What’s Next

path_deny is a manual mechanism: you have to know which files to exclude and tell the subagent. There’s room for:

  • Default deny lists: Auto-deny common sensitive patterns (*.secret, *.env, .git/config)
  • Automatic size-based denial: Skip files over a threshold without explicit patterns
  • Context mode integration: The existing context_mode="selective" parameter (shipped in PR #2950) plus path_deny gives two complementary isolation axes — what to include and what to exclude

But the manual-first approach is intentional. Explicit opt-in to file exclusion means no surprising context gaps. You know exactly what your subagent isn’t seeing.