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.
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.ymlthat 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.mdwith unannounced features or partnership details. - Log files: Gigabyte-scale application logs a sibling watcher writes — no subagent needs them, but they match
*.logand 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:
- Full path — match
/home/user/project/config/prod.ymldirectly - Workspace-relative path — match
config/prod.yml - 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— newpath_deny: list[str] | Nonefield on theSubagentdataclassgptme/tools/subagent/api.py—subagent()exposes the parametergptme/tools/subagent/context.py—apply_path_deny()implementationgptme/tools/subagent/execution.py— wires through all three execution pathstests/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) pluspath_denygives 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.