
Filesystem Context
- 406 installs
- 941 repo stars
- Updated August 5, 2026
- guanyang/antigravity-skills
filesystem-context is an agent skill that packages repository trees, offloaded tool outputs, and structured scratch files so coding agents retrieve project context on demand without filling every prompt turn.
About
filesystem-context is an antigravity-skills agent skill (version 1.2.0) that treats the filesystem as the primary overflow layer for coding-agent context engineering. It documents six patterns—scratch-pad offloading above a ~2000-token threshold, YAML plan persistence, sub-agent workspace directories, dynamic SKILL.md loading, terminal log files, and preference self-modification with validation guards. The guide diagnoses four context failure modes (missing, under-retrieved, over-retrieved, buried) and prescribes grep, glob, and line-range reads for selective retrieval. Developers reach for filesystem-context when tool outputs bloat prompts, multi-turn tasks need durable state, or sub-agents must hand off findings without message-chain summarization loss. Eleven guidelines and eight gotchas cover retention policies, race conditions, and path hygiene for production agent workflows.
- Summarizes repo trees with ignore rules
- Selects excerpts over full-file dumps
- Maps paths to tasks and symbols
- Cuts noise from node_modules and build dirs
- Improves multi-file refactor accuracy
Filesystem Context by the numbers
- 406 all-time installs (skills.sh)
- Ranked #1,947 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/guanyang/antigravity-skills --skill filesystem-contextAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 406 |
|---|---|
| repo stars | ★ 941 |
| Last updated | August 5, 2026 |
| Repository | guanyang/antigravity-skills ↗ |
How do coding agents manage context beyond the window?
Package repository trees, path summaries, and file excerpts so coding agents see the right project layout without dumping entire directories into every prompt turn.
Who is it for?
Agent developers building long-horizon Claude Code or Cursor workflows where tool outputs, logs, and plans exceed comfortable context limits.
Skip if: Single-turn tasks that fit comfortably in the context window or teams needing semantic cross-session memory graphs should use memory-systems instead.
When should I use this skill?
Tool outputs exceed roughly 2000 tokens, agents need cross-turn plan persistence, or sub-agents must share state through files instead of message chains.
What you get
Scratch files, YAML plan documents, sub-agent workspace directories, and file references replacing bloated prompt history.
- Scratch file references
- Structured plan YAML
- Sub-agent workspace directories
By the numbers
- Skill version 1.2.0 in guanyang/antigravity-skills
- Documents 6 core filesystem context patterns
- Recommends offloading tool outputs above ~2000-token threshold
Files
Filesystem-Based Context Engineering
Use the filesystem as the primary overflow layer for agent context because context windows are limited while tasks often require more information than fits in a single window. Files let agents store, retrieve, and update an effectively unlimited amount of context through a single interface.
Prefer dynamic context discovery -- pulling relevant context on demand -- over static inclusion, because static context consumes tokens regardless of relevance and crowds out space for task-specific information.
When to Activate
Activate this skill when:
- Tool outputs are bloating the context window
- Agents need to persist state across long trajectories
- Sub-agents must share information without direct message passing
- Tasks require more context than fits in the window
- Building agents that learn and update their own instructions
- Implementing scratch pads for intermediate results
- Terminal outputs or logs need to be accessible to agents
Core Concepts
Diagnose context failures against these four modes, because each requires a different filesystem remedy:
1. Missing context -- needed information is absent from the total available context. Fix by persisting tool outputs and intermediate results to files so nothing is lost. 2. Under-retrieved context -- retrieved content fails to encapsulate what the agent needs. Fix by structuring files for targeted retrieval (grep-friendly formats, clear section headers). 3. Over-retrieved context -- retrieved content far exceeds what is needed, wasting tokens and degrading attention. Fix by offloading bulk content to files and returning compact references. 4. Buried context -- niche information is hidden across many files. Fix by combining glob and grep for structural search alongside semantic search for conceptual queries.
Use the filesystem as the persistent layer that addresses all four: write once, store durably, retrieve selectively.
Detailed Topics
The Static vs Dynamic Context Trade-off
Treat static context (system instructions, tool definitions, critical rules) as expensive real estate -- it consumes tokens on every turn regardless of relevance. As agents accumulate capabilities, static context grows and crowds out dynamic information.
Use dynamic context discovery instead: include only minimal static pointers (names, one-line descriptions, file paths) and load full content with search tools when relevant. This is more token-efficient and often improves response quality by reducing contradictory or irrelevant information in the window.
Accept the trade-off: dynamic discovery requires the model to recognize when it needs more context. Current frontier models handle this well, but less capable models may fail to trigger loads. When in doubt, err toward including critical safety or correctness constraints statically.
Pattern 1: Filesystem as Scratch Pad
Redirect large tool outputs to files instead of returning them directly to context, because a single web search or database query can dump thousands of tokens into message history where they persist for the entire conversation.
Write the output to a scratch file, extract a compact summary, and return a file reference. The agent then uses targeted retrieval (grep for patterns, read with line ranges) to access only what it needs.
def handle_tool_output(output: str, threshold: int = 2000) -> str:
if len(output) < threshold:
return output
file_path = f"scratch/{tool_name}_{timestamp}.txt"
write_file(file_path, output)
key_summary = extract_summary(output, max_tokens=200)
return f"[Output written to {file_path}. Summary: {key_summary}]"Use grep to search the offloaded file and read_file with line ranges to retrieve targeted sections, because this preserves full output for later reference while keeping only ~100 tokens in the active context.
Pattern 2: Plan Persistence
Write plans to the filesystem because long-horizon tasks lose coherence when plans fall out of attention or get summarized away. The agent re-reads its plan at any point, restoring awareness of the objective and progress.
Store plans in structured format so they are both human-readable and machine-parseable:
# scratch/current_plan.yaml
objective: "Refactor authentication module"
status: in_progress
steps:
- id: 1
description: "Audit current auth endpoints"
status: completed
- id: 2
description: "Design new token validation flow"
status: in_progress
- id: 3
description: "Implement and test changes"
status: pendingRe-read the plan at the start of each turn or after any context refresh to re-orient, because this acts as "manipulating attention through recitation."
Pattern 3: Sub-Agent Communication via Filesystem
Route sub-agent findings through the filesystem instead of message passing, because multi-hop message chains degrade information through summarization at each hop ("game of telephone").
Have each sub-agent write directly to its own workspace directory. The coordinator reads these files directly, preserving full fidelity:
workspace/
agents/
research_agent/
findings.md
sources.jsonl
code_agent/
changes.md
test_results.txt
coordinator/
synthesis.mdEnforce per-agent directory isolation to prevent write conflicts and maintain clear ownership of each output artifact.
Pattern 4: Dynamic Skill Loading
Store skills as files and include only skill names with brief descriptions in static context, because stuffing all instructions into the system prompt wastes tokens and can confuse the model with contradictory guidance.
Available skills (load with read_file when relevant):
- database-optimization: Query tuning and indexing strategies
- api-design: REST/GraphQL best practices
- testing-strategies: Unit, integration, and e2e testing patternsLoad the full skill file (e.g., skills/database-optimization/SKILL.md) only when the current task requires it. This converts O(n) static token cost into O(1) per task.
Pattern 5: Terminal and Log Persistence
Persist terminal output to files automatically and use grep for selective retrieval, because terminal output from long-running processes accumulates rapidly and manual copy-paste is error-prone.
terminals/
1.txt # Terminal session 1 output
2.txt # Terminal session 2 outputQuery with targeted grep (grep -A 5 "error" terminals/1.txt) instead of loading entire terminal histories into context.
Pattern 6: Learning Through Self-Modification
Have agents write learned preferences and patterns to their own instruction files so subsequent sessions load this context automatically, instead of requiring manual system prompt updates.
def remember_preference(key: str, value: str):
preferences_file = "agent/user_preferences.yaml"
prefs = load_yaml(preferences_file)
prefs[key] = value
write_yaml(preferences_file, prefs)Guard this pattern with validation because self-modification can accumulate incorrect or contradictory instructions over time. Treat it as experimental -- review persisted preferences periodically.
Filesystem Search Techniques
Combine ls/list_dir, glob, grep, and read_file with line ranges for context discovery, because models are specifically trained on filesystem traversal and this combination often outperforms semantic search for technical content where structural patterns are clear.
ls/list_dir: Discover directory structureglob: Find files matching patterns (e.g.,**/*.py)grep: Search file contents, returns matching lines with contextread_filewith ranges: Read specific sections without loading entire files
Use filesystem search for structural and exact-match queries, and semantic search for conceptual queries. Combine both for comprehensive discovery.
Practical Guidance
When to Use Filesystem Context
Apply filesystem patterns when the situation matches these criteria, because they add I/O overhead that is only justified by token savings or persistence needs:
Use when:
- Tool outputs exceed ~2000 tokens
- Tasks span multiple conversation turns
- Multiple agents need shared state
- Skills or instructions exceed comfortable system prompt size
- Logs or terminal output need selective querying
Avoid when:
- Tasks complete in single turns (overhead not justified)
- Context fits comfortably in window (no problem to solve)
- Latency is critical (file I/O adds measurable delay)
- Model lacks filesystem tool capabilities
File Organization
Structure files for agent discoverability, because agents navigate by listing and reading directory names:
project/
scratch/ # Temporary working files
tool_outputs/ # Large tool results
plans/ # Active plans and checklists
memory/ # Persistent learned information
preferences.yaml # User preferences
patterns.md # Learned patterns
skills/ # Loadable skill definitions
agents/ # Sub-agent workspacesUse consistent naming conventions and include timestamps or IDs in scratch files for disambiguation.
Token Accounting
Measure where tokens originate before and after applying filesystem patterns, because optimizing without measurement leads to wasted effort:
- Track static vs dynamic context ratio
- Monitor tool output sizes before and after offloading
- Measure how often dynamically-loaded context is actually used
Examples
Example 1: Tool Output Offloading
Input: Web search returns 8000 tokens
Before: 8000 tokens added to message history
After:
- Write to scratch/search_results_001.txt
- Return: "[Results in scratch/search_results_001.txt. Key finding: API rate limit is 1000 req/min]"
- Agent greps file when needing specific details
Result: ~100 tokens in context, 8000 tokens accessible on demandExample 2: Dynamic Skill Loading
Input: User asks about database indexing
Static context: "database-optimization: Query tuning and indexing"
Agent action: read_file("skills/database-optimization/SKILL.md")
Result: Full skill loaded only when relevantExample 3: Chat History as File Reference
Trigger: Context window limit reached, summarization required
Action:
1. Write full history to history/session_001.txt
2. Generate summary for new context window
3. Include reference: "Full history in history/session_001.txt"
Result: Agent can search history file to recover details lost in summarizationGuidelines
1. Write large outputs to files; return summaries and references to context 2. Store plans and state in structured files for re-reading 3. Use sub-agent file workspaces instead of message chains 4. Load skills dynamically rather than stuffing all into system prompt 5. Persist terminal and log output as searchable files 6. Combine grep/glob with semantic search for comprehensive discovery 7. Organize files for agent discoverability with clear naming 8. Measure token savings to validate filesystem patterns are effective 9. Implement cleanup for scratch files to prevent unbounded growth 10. Guard self-modification patterns with validation
Gotchas
1. Scratch directory unbounded growth: Agents create temp files without cleanup, eventually consuming disk and making directory listings noisy. Implement a retention policy (age-based or count-based) and run cleanup at session boundaries. 2. Race conditions in multi-agent file access: Concurrent writes to the same file corrupt state silently. Enforce per-agent directory isolation or use append-only files with agent-prefixed entries. 3. Stale file references after moves/renames: Agents hold paths from prior turns that no longer exist after refactors or file reorganization. Always verify file existence before reading a cached path; re-discover with glob if the check fails. 4. Glob pattern false matches: Overly broad patterns (e.g., **/*) pull irrelevant files into context, wasting tokens and confusing the model. Scope globs to specific directories and extensions. 5. File size assumptions: Reading a file without checking size can dump 100K+ tokens into context in a single tool call. Check file size before reading; use line-range reads for large files. 6. Missing file existence checks: Agents assume files exist from prior turns, but they may have been deleted or moved. Always guard reads with existence checks and handle missing-file errors gracefully. 7. Scratch pad format drift: Unstructured scratch pads become unparseable after many writes because format conventions erode over successive appends. Define and enforce a schema (YAML, JSON, or structured markdown) from the first write. 8. Hardcoded absolute paths: Break when repositories are checked out at different locations or when running in containers. Use relative paths from the project root or resolve paths dynamically.
Integration
This skill connects to:
- context-optimization - Filesystem offloading is a form of observation masking
- memory-systems - Filesystem-as-memory is a simple memory layer
- multi-agent-patterns - Sub-agent file workspaces enable isolation
- context-compression - File references enable lossless "compression"
- tool-design - Tools should return file references for large outputs
References
Internal reference:
- Implementation Patterns - Read when: implementing scratch pad, plan persistence, or tool output offloading and need concrete code beyond the inline examples
Related skills in this collection:
- context-optimization - Read when: applying token reduction techniques alongside filesystem offloading
- memory-systems - Read when: building persistent storage that outlasts a single session
- multi-agent-patterns - Read when: designing agent coordination with shared file workspaces
External resources:
- LangChain Deep Agents — Read when: implementing filesystem-based context patterns in LangChain/LangGraph pipelines
- Cursor context discovery — Read when: studying how production IDEs implement dynamic context loading
- Anthropic Agent Skills specification — Read when: building skills that leverage filesystem progressive disclosure
---
Skill Metadata
Created: 2026-01-07 Last Updated: 2026-03-17 Author: Agent Skills for Context Engineering Contributors Version: 1.1.0
Filesystem Context Implementation Patterns
This reference provides detailed implementation patterns for filesystem-based context engineering.
Pattern Catalog
1. Scratch Pad Manager
A centralized manager for handling large tool outputs and intermediate results.
import os
import json
from datetime import datetime
from pathlib import Path
class ScratchPadManager:
"""Manages temporary file storage for agent context offloading."""
def __init__(self, base_path: str = "scratch", token_threshold: int = 2000):
self.base_path = Path(base_path)
self.base_path.mkdir(parents=True, exist_ok=True)
self.token_threshold = token_threshold
self.manifest = {}
def should_offload(self, content: str) -> bool:
"""Determine if content exceeds threshold for offloading."""
# Rough token estimate: 1 token ≈ 4 characters
estimated_tokens = len(content) // 4
return estimated_tokens > self.token_threshold
def offload(self, content: str, source: str, summary: str = None) -> dict:
"""Write content to file, return reference."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{source}_{timestamp}.txt"
file_path = self.base_path / filename
file_path.write_text(content)
reference = {
"type": "file_reference",
"path": str(file_path),
"source": source,
"timestamp": timestamp,
"size_chars": len(content),
"summary": summary or self._extract_summary(content)
}
self.manifest[filename] = reference
return reference
def _extract_summary(self, content: str, max_chars: int = 500) -> str:
"""Extract first meaningful content as summary."""
lines = content.strip().split('\n')
summary_lines = []
char_count = 0
for line in lines:
if char_count + len(line) > max_chars:
break
summary_lines.append(line)
char_count += len(line)
return '\n'.join(summary_lines)
def cleanup(self, max_age_hours: int = 24):
"""Remove scratch files older than threshold."""
cutoff = datetime.now().timestamp() - (max_age_hours * 3600)
for file_path in self.base_path.glob("*.txt"):
if file_path.stat().st_mtime < cutoff:
file_path.unlink()
if file_path.name in self.manifest:
del self.manifest[file_path.name]2. Plan Persistence
Structured plan storage with progress tracking.
import yaml
from dataclasses import dataclass, field, asdict
from enum import Enum
from typing import List, Optional
class StepStatus(Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
BLOCKED = "blocked"
CANCELLED = "cancelled"
@dataclass
class PlanStep:
id: int
description: str
status: StepStatus = StepStatus.PENDING
notes: Optional[str] = None
@dataclass
class AgentPlan:
objective: str
steps: List[PlanStep] = field(default_factory=list)
status: str = "in_progress"
def save(self, path: str = "scratch/current_plan.yaml"):
"""Persist plan to filesystem."""
data = {
"objective": self.objective,
"status": self.status,
"steps": [
{
"id": s.id,
"description": s.description,
"status": s.status.value,
"notes": s.notes
}
for s in self.steps
]
}
with open(path, 'w') as f:
yaml.dump(data, f, default_flow_style=False)
@classmethod
def load(cls, path: str = "scratch/current_plan.yaml") -> "AgentPlan":
"""Load plan from filesystem."""
with open(path, 'r') as f:
data = yaml.safe_load(f)
plan = cls(objective=data["objective"], status=data.get("status", "in_progress"))
for step_data in data.get("steps", []):
plan.steps.append(PlanStep(
id=step_data["id"],
description=step_data["description"],
status=StepStatus(step_data["status"]),
notes=step_data.get("notes")
))
return plan
def current_step(self) -> Optional[PlanStep]:
"""Get the first non-completed step."""
for step in self.steps:
if step.status != StepStatus.COMPLETED:
return step
return None
def complete_step(self, step_id: int, notes: str = None):
"""Mark step as completed."""
for step in self.steps:
if step.id == step_id:
step.status = StepStatus.COMPLETED
if notes:
step.notes = notes
break3. Sub-Agent Workspace
File-based communication between agents.
from pathlib import Path
from datetime import datetime
import json
class AgentWorkspace:
"""Manages file-based workspace for an agent."""
def __init__(self, agent_id: str, base_path: str = "workspace/agents"):
self.agent_id = agent_id
self.path = Path(base_path) / agent_id
self.path.mkdir(parents=True, exist_ok=True)
# Standard files
self.findings_file = self.path / "findings.md"
self.status_file = self.path / "status.json"
self.log_file = self.path / "activity.log"
def write_finding(self, content: str, append: bool = True):
"""Write or append a finding."""
mode = 'a' if append else 'w'
with open(self.findings_file, mode) as f:
if append:
f.write(f"\n---\n## {datetime.now().isoformat()}\n\n")
f.write(content)
def update_status(self, status: str, progress: float = None, details: dict = None):
"""Update agent status for coordinator visibility."""
status_data = {
"agent_id": self.agent_id,
"status": status,
"updated_at": datetime.now().isoformat(),
"progress": progress,
"details": details or {}
}
self.status_file.write_text(json.dumps(status_data, indent=2))
def log(self, message: str):
"""Append to activity log."""
with open(self.log_file, 'a') as f:
f.write(f"[{datetime.now().isoformat()}] {message}\n")
def read_peer_findings(self, peer_id: str) -> str:
"""Read findings from another agent's workspace."""
peer_path = self.path.parent / peer_id / "findings.md"
if peer_path.exists():
return peer_path.read_text()
return ""
class CoordinatorWorkspace:
"""Coordinator that reads from sub-agent workspaces."""
def __init__(self, base_path: str = "workspace/agents"):
self.base_path = Path(base_path)
def get_all_statuses(self) -> dict:
"""Collect status from all sub-agents."""
statuses = {}
for agent_dir in self.base_path.iterdir():
if agent_dir.is_dir():
status_file = agent_dir / "status.json"
if status_file.exists():
statuses[agent_dir.name] = json.loads(status_file.read_text())
return statuses
def aggregate_findings(self) -> str:
"""Combine all agent findings into synthesis."""
findings = []
for agent_dir in self.base_path.iterdir():
if agent_dir.is_dir():
findings_file = agent_dir / "findings.md"
if findings_file.exists():
findings.append(f"# {agent_dir.name}\n\n{findings_file.read_text()}")
return "\n\n".join(findings)4. Dynamic Skill Loader
Load skill content on demand.
from pathlib import Path
from typing import List, Optional
import yaml
@dataclass
class SkillMetadata:
name: str
description: str
path: str
triggers: List[str] = field(default_factory=list)
class SkillLoader:
"""Manages dynamic loading of agent skills."""
def __init__(self, skills_path: str = "skills"):
self.skills_path = Path(skills_path)
self.skill_index = self._build_index()
def _build_index(self) -> dict:
"""Build index of available skills from SKILL.md frontmatter."""
index = {}
for skill_dir in self.skills_path.iterdir():
if skill_dir.is_dir():
skill_file = skill_dir / "SKILL.md"
if skill_file.exists():
metadata = self._parse_frontmatter(skill_file)
if metadata:
index[metadata.name] = metadata
return index
def _parse_frontmatter(self, path: Path) -> Optional[SkillMetadata]:
"""Extract YAML frontmatter from skill file."""
content = path.read_text()
if content.startswith('---'):
end = content.find('---', 3)
if end > 0:
frontmatter = yaml.safe_load(content[3:end])
return SkillMetadata(
name=frontmatter.get('name', path.parent.name),
description=frontmatter.get('description', ''),
path=str(path),
triggers=frontmatter.get('triggers', [])
)
return None
def get_static_context(self) -> str:
"""Generate minimal static context listing available skills."""
lines = ["Available skills (load with read_file when relevant):"]
for name, meta in self.skill_index.items():
lines.append(f"- {name}: {meta.description[:100]}")
return "\n".join(lines)
def load_skill(self, name: str) -> str:
"""Load full skill content."""
if name in self.skill_index:
return Path(self.skill_index[name].path).read_text()
raise ValueError(f"Unknown skill: {name}")
def find_relevant_skills(self, query: str) -> List[str]:
"""Find skills that might be relevant to a query."""
query_lower = query.lower()
relevant = []
for name, meta in self.skill_index.items():
if any(trigger in query_lower for trigger in meta.triggers):
relevant.append(name)
elif name.replace('-', ' ') in query_lower:
relevant.append(name)
return relevant5. Terminal Output Persistence
Capture and persist terminal sessions.
import subprocess
from pathlib import Path
from datetime import datetime
import re
class TerminalCapture:
"""Captures and persists terminal output for agent access."""
def __init__(self, terminals_path: str = "terminals"):
self.terminals_path = Path(terminals_path)
self.terminals_path.mkdir(parents=True, exist_ok=True)
self.session_counter = 0
def run_command(self, command: str, capture: bool = True) -> dict:
"""Run command and optionally capture output to file."""
self.session_counter += 1
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True
)
output = {
"command": command,
"exit_code": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
"timestamp": datetime.now().isoformat()
}
if capture:
output["file"] = self._persist_output(output)
return output
def _persist_output(self, output: dict) -> str:
"""Write output to terminal file."""
filename = f"{self.session_counter}.txt"
file_path = self.terminals_path / filename
content = f"""---
command: {output['command']}
exit_code: {output['exit_code']}
timestamp: {output['timestamp']}
---
=== STDOUT ===
{output['stdout']}
=== STDERR ===
{output['stderr']}
"""
file_path.write_text(content)
return str(file_path)
def grep_terminals(self, pattern: str, context_lines: int = 3) -> List[dict]:
"""Search all terminal outputs for pattern."""
matches = []
regex = re.compile(pattern, re.IGNORECASE)
for term_file in self.terminals_path.glob("*.txt"):
content = term_file.read_text()
lines = content.split('\n')
for i, line in enumerate(lines):
if regex.search(line):
start = max(0, i - context_lines)
end = min(len(lines), i + context_lines + 1)
matches.append({
"file": str(term_file),
"line_number": i + 1,
"context": '\n'.join(lines[start:end])
})
return matches6. Self-Modification Guard
Safe pattern for agent self-learning.
import yaml
from pathlib import Path
from datetime import datetime
from typing import Any
class PreferenceStore:
"""Guarded storage for agent-learned preferences."""
MAX_ENTRIES = 100
MAX_VALUE_LENGTH = 1000
def __init__(self, path: str = "agent/preferences.yaml"):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.preferences = self._load()
def _load(self) -> dict:
"""Load preferences from file."""
if self.path.exists():
return yaml.safe_load(self.path.read_text()) or {}
return {}
def _save(self):
"""Persist preferences to file."""
self.path.write_text(yaml.dump(self.preferences, default_flow_style=False))
def remember(self, key: str, value: Any, source: str = "user"):
"""Store a preference with validation."""
# Validate key
if not key or len(key) > 100:
raise ValueError("Invalid key length")
# Validate value
value_str = str(value)
if len(value_str) > self.MAX_VALUE_LENGTH:
raise ValueError(f"Value exceeds max length of {self.MAX_VALUE_LENGTH}")
# Check entry limit
if len(self.preferences) >= self.MAX_ENTRIES and key not in self.preferences:
raise ValueError(f"Max entries ({self.MAX_ENTRIES}) reached")
# Store with metadata
self.preferences[key] = {
"value": value,
"source": source,
"updated_at": datetime.now().isoformat()
}
self._save()
def recall(self, key: str, default: Any = None) -> Any:
"""Retrieve a preference."""
entry = self.preferences.get(key)
if entry:
return entry["value"]
return default
def list_all(self) -> dict:
"""Get all preferences for context injection."""
return {k: v["value"] for k, v in self.preferences.items()}
def forget(self, key: str):
"""Remove a preference."""
if key in self.preferences:
del self.preferences[key]
self._save()Integration Example
Combining patterns in an agent harness:
class FilesystemContextAgent:
"""Agent with filesystem-based context management."""
def __init__(self):
self.scratch = ScratchPadManager()
self.skills = SkillLoader()
self.preferences = PreferenceStore()
self.workspace = AgentWorkspace("main_agent")
def handle_tool_output(self, tool_name: str, output: str) -> str:
"""Process tool output, offloading if necessary."""
if self.scratch.should_offload(output):
ref = self.scratch.offload(output, source=tool_name)
return f"[{tool_name} output saved to {ref['path']}. Summary: {ref['summary'][:200]}]"
return output
def get_system_prompt(self) -> str:
"""Build system prompt with dynamic skill references."""
base_prompt = "You are a helpful assistant."
skill_context = self.skills.get_static_context()
user_prefs = self.preferences.list_all()
pref_section = ""
if user_prefs:
pref_section = "\n\nUser preferences:\n" + "\n".join(
f"- {k}: {v}" for k, v in user_prefs.items()
)
return f"{base_prompt}\n\n{skill_context}{pref_section}"File Organization Best Practices
project/
├── scratch/ # Ephemeral working files
│ ├── tool_outputs/ # Large tool results
│ │ └── search_20260107.txt
│ └── plans/ # Active task plans
│ └── current_plan.yaml
├── workspace/ # Agent workspaces
│ └── agents/
│ ├── research_agent/
│ │ ├── findings.md
│ │ └── status.json
│ └── code_agent/
│ ├── findings.md
│ └── status.json
├── agent/ # Agent configuration
│ ├── preferences.yaml # Learned preferences
│ └── patterns.md # Discovered patterns
├── skills/ # Loadable skills
│ └── {skill-name}/
│ └── SKILL.md
├── terminals/ # Terminal output
│ ├── 1.txt
│ └── 2.txt
└── history/ # Chat history archives
└── session_001.txtToken Accounting Metrics
Track these metrics to validate filesystem patterns:
1. Static context ratio: tokens in static context / total tokens 2. Dynamic load rate: how often skills/files are loaded per task 3. Offload savings: tokens saved by writing to files vs keeping in context 4. Retrieval precision: percentage of loaded content actually used
Target benchmarks:
- Static context ratio < 20%
- Offload savings > 50% for tool-heavy workflows
- Retrieval precision > 70% (loaded content is relevant)
"""
Filesystem Context Manager -- composable utilities for filesystem-based context engineering.
Provides three core patterns for managing agent context through the filesystem:
1. ScratchPadManager -- offload large tool outputs to files, return compact references
2. AgentPlan / PlanStep -- persist plans to disk so agents survive context window refreshes
3. ToolOutputHandler -- automatic offload-or-inline decision for tool outputs
Use when:
- Tool outputs exceed ~2000 tokens and would bloat the context window
- Agents need plan persistence across long-horizon, multi-turn tasks
- Building agent systems that offload intermediate results to files
Example (library usage)::
from filesystem_context import ScratchPadManager, ToolOutputHandler
handler = ToolOutputHandler(ScratchPadManager(base_path="scratch"))
result = handler.process_output("web_search", large_output_string)
Example (CLI demo)::
python filesystem_context.py
"""
from __future__ import annotations
import json
import os
import shutil
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
__all__: list[str] = [
"ScratchPadManager",
"PlanStep",
"AgentPlan",
"ToolOutputHandler",
]
# =============================================================================
# Pattern 1: Scratch Pad Manager
# =============================================================================
class ScratchPadManager:
"""Manage temporary file storage for offloading large tool outputs.
Use when: tool outputs exceed a token threshold and would bloat the
context window. Writes content to a scratch directory and returns a
compact reference the agent can include in context instead.
"""
def __init__(self, base_path: str = "scratch", token_threshold: int = 2000) -> None:
self.base_path: Path = Path(base_path)
self.base_path.mkdir(parents=True, exist_ok=True)
self.token_threshold: int = token_threshold
def estimate_tokens(self, content: str) -> int:
"""Return a rough token estimate (~4 characters per token).
Use when: deciding whether content should be offloaded before
writing it to disk.
"""
return len(content) // 4
def should_offload(self, content: str) -> bool:
"""Return True if *content* exceeds the configured token threshold.
Use when: making an inline-vs-offload decision for a tool output.
"""
return self.estimate_tokens(content) > self.token_threshold
def offload(self, content: str, source: str) -> Dict[str, Any]:
"""Write *content* to a timestamped scratch file and return a reference dict.
Use when: a tool output has been determined to exceed the threshold
and should be persisted to disk.
Returns a dict with keys: path, source, tokens_saved, summary.
"""
timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
filename: str = f"{source}_{timestamp}.txt"
file_path: Path = self.base_path / filename
file_path.write_text(content)
# Extract summary from first meaningful lines
lines: list[str] = content.strip().split("\n")[:5]
summary: str = "\n".join(lines)
if len(summary) > 300:
summary = summary[:300] + "..."
return {
"path": str(file_path),
"source": source,
"tokens_saved": self.estimate_tokens(content),
"summary": summary,
}
def format_reference(self, ref: Dict[str, Any]) -> str:
"""Format a reference dict as a compact string for context inclusion.
Use when: constructing the replacement message that goes into context
in place of the full tool output.
"""
return (
f"[Output from {ref['source']} saved to {ref['path']}. "
f"~{ref['tokens_saved']} tokens. "
f"Summary: {ref['summary'][:200]}]"
)
def cleanup(self, max_age_seconds: int = 3600) -> int:
"""Remove scratch files older than *max_age_seconds*.
Use when: ending a session or when the scratch directory has grown
large enough to slow directory listings.
Returns the number of files removed.
"""
removed: int = 0
now: float = datetime.now().timestamp()
for f in self.base_path.iterdir():
if f.is_file() and (now - f.stat().st_mtime) > max_age_seconds:
f.unlink()
removed += 1
return removed
# =============================================================================
# Pattern 2: Plan Persistence
# =============================================================================
@dataclass
class PlanStep:
"""Individual step in an agent plan.
Use when: building a plan that will be persisted to disk for later
re-reading across context window boundaries.
"""
id: int
description: str
status: str = "pending" # pending | in_progress | completed | blocked
notes: Optional[str] = None
@dataclass
class AgentPlan:
"""Persistent plan that survives context window limitations.
Use when: an agent needs to track a multi-step objective across turns
or context refreshes. Write the plan to disk so the agent can re-read
it at any point, even after summarization or context window refresh.
"""
objective: str
steps: List[PlanStep] = field(default_factory=list)
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
def to_dict(self) -> Dict[str, Any]:
"""Serialize the plan to a plain dict suitable for JSON output."""
return {
"objective": self.objective,
"created_at": self.created_at,
"steps": [
{
"id": s.id,
"description": s.description,
"status": s.status,
"notes": s.notes,
}
for s in self.steps
],
}
def save(self, path: str = "scratch/current_plan.json") -> None:
"""Persist plan to *path* as JSON.
Use when: a plan has been created or updated and must survive a
potential context refresh.
"""
Path(path).parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
json.dump(self.to_dict(), f, indent=2)
print(f"Plan saved to {path}")
@classmethod
def load(cls, path: str = "scratch/current_plan.json") -> AgentPlan:
"""Load a plan from *path*.
Use when: resuming work in a new context window or after
summarization -- re-read the plan to restore task awareness.
"""
with open(path, "r") as f:
data: Dict[str, Any] = json.load(f)
plan = cls(objective=data["objective"])
plan.created_at = data.get("created_at", "")
for step_data in data.get("steps", []):
plan.steps.append(
PlanStep(
id=step_data["id"],
description=step_data["description"],
status=step_data["status"],
notes=step_data.get("notes"),
)
)
return plan
def current_step(self) -> Optional[PlanStep]:
"""Return the first non-completed step, or None if all are done.
Use when: determining what to work on next after re-reading a plan.
"""
for step in self.steps:
if step.status not in ("completed", "cancelled"):
return step
return None
def complete_step(self, step_id: int, notes: Optional[str] = None) -> None:
"""Mark step *step_id* as completed, optionally attaching *notes*.
Use when: an agent finishes a plan step and needs to record
progress before persisting the updated plan.
"""
for step in self.steps:
if step.id == step_id:
step.status = "completed"
if notes:
step.notes = notes
return
raise ValueError(f"Step {step_id} not found")
def progress_summary(self) -> str:
"""Generate a compact progress string for context injection.
Use when: the agent needs a one-line status to include in context
without re-reading the full plan.
"""
completed: int = sum(1 for s in self.steps if s.status == "completed")
total: int = len(self.steps)
current: Optional[PlanStep] = self.current_step()
summary: str = f"Objective: {self.objective}\n"
summary += f"Progress: {completed}/{total} steps completed\n"
if current:
summary += f"Current step: [{current.id}] {current.description}"
else:
summary += "All steps completed."
return summary
# =============================================================================
# Pattern 3: Tool Output Handler
# =============================================================================
class ToolOutputHandler:
"""Automatically decide whether to inline or offload tool outputs.
Use when: building an agent loop that processes heterogeneous tool
outputs -- some small enough to inline, others requiring offload.
"""
def __init__(self, scratch_pad: Optional[ScratchPadManager] = None) -> None:
self.scratch_pad: ScratchPadManager = scratch_pad or ScratchPadManager()
def process_output(self, tool_name: str, output: str) -> str:
"""Return *output* directly if small, or a file reference if large.
Use when: handling a tool's return value in an agent loop. Pass
the result into context; offloading happens transparently.
"""
if self.scratch_pad.should_offload(output):
ref: Dict[str, Any] = self.scratch_pad.offload(output, source=tool_name)
return self.scratch_pad.format_reference(ref)
return output
# =============================================================================
# Demonstration
# =============================================================================
def _demo_scratch_pad() -> None:
"""Demonstrate the scratch pad offloading pattern."""
print("=" * 60)
print("DEMO: Scratch Pad for Tool Output Offloading")
print("=" * 60)
scratch = ScratchPadManager(base_path="demo_scratch", token_threshold=100)
# Small output stays in context
small_output: str = "API returned: {'status': 'ok', 'data': [1, 2, 3]}"
print(f"\nSmall output ({scratch.estimate_tokens(small_output)} tokens):")
print(f" Should offload: {scratch.should_offload(small_output)}")
# Large output gets offloaded
large_output: str = """
Search Results for "context engineering":
1. Context Engineering: The Art of Curating LLM Context
URL: https://example.com/article1
Snippet: Context engineering is the discipline of managing what information
enters the language model's context window. Unlike prompt engineering which
focuses on instruction crafting, context engineering addresses the holistic
curation of all information...
2. Building Production Agents with Effective Context Management
URL: https://example.com/article2
Snippet: Production agent systems require sophisticated context management
strategies. This includes compression, caching, and strategic partitioning
of work across sub-agents with isolated contexts...
3. The Lost-in-Middle Problem and How to Avoid It
URL: https://example.com/article3
Snippet: Research shows that language models exhibit U-shaped attention
patterns, with information in the middle of long contexts receiving less
attention than content at the beginning or end...
... (imagine 50 more results) ...
"""
print(f"\nLarge output ({scratch.estimate_tokens(large_output)} tokens):")
print(f" Should offload: {scratch.should_offload(large_output)}")
if scratch.should_offload(large_output):
ref = scratch.offload(large_output, source="web_search")
print(f"\nOffloaded to: {ref['path']}")
print(f"Tokens saved: {ref['tokens_saved']}")
print(f"\nReference for context:\n{scratch.format_reference(ref)}")
def _demo_plan_persistence() -> None:
"""Demonstrate the plan persistence pattern."""
print("\n" + "=" * 60)
print("DEMO: Plan Persistence for Long-Horizon Tasks")
print("=" * 60)
plan = AgentPlan(objective="Refactor authentication module")
plan.steps = [
PlanStep(id=1, description="Audit current auth endpoints"),
PlanStep(id=2, description="Design new token validation flow"),
PlanStep(id=3, description="Implement changes"),
PlanStep(id=4, description="Write tests"),
PlanStep(id=5, description="Deploy and monitor"),
]
print("\nInitial plan:")
print(plan.progress_summary())
plan.save("demo_scratch/current_plan.json")
# Simulate completing first step
plan.complete_step(1, notes="Found 12 endpoints, 3 need updates")
plan.steps[1].status = "in_progress"
print("\nAfter completing step 1:")
print(plan.progress_summary())
plan.save("demo_scratch/current_plan.json")
# Simulate loading from file (as if in new context)
print("\n--- Simulating context refresh ---")
loaded_plan = AgentPlan.load("demo_scratch/current_plan.json")
print("\nPlan loaded from file:")
print(loaded_plan.progress_summary())
def _demo_tool_handler() -> None:
"""Demonstrate the integrated tool output handler."""
print("\n" + "=" * 60)
print("DEMO: Integrated Tool Output Handler")
print("=" * 60)
handler = ToolOutputHandler(
scratch_pad=ScratchPadManager(base_path="demo_scratch", token_threshold=50)
)
outputs: list[tuple[str, str]] = [
("calculator", "42"),
("file_read", "Error: File not found"),
(
"database_query",
"""
Results (250 rows):
| id | name | email | created_at | status |
|----|------|-------|------------|--------|
| 1 | John | j@e.c | 2024-01-01 | active |
| 2 | Jane | j@e.c | 2024-01-02 | active |
... (248 more rows) ...
""",
),
]
for tool_name, output in outputs:
processed: str = handler.process_output(tool_name, output)
print(f"\n{tool_name}:")
print(f" Original length: {len(output)} chars")
print(f" Processed: {processed[:100]}...")
def _cleanup_demo() -> None:
"""Remove demo files created during the demonstration."""
demo_path = Path("demo_scratch")
if demo_path.exists():
shutil.rmtree(demo_path)
print("\nDemo files cleaned up.")
if __name__ == "__main__":
_demo_scratch_pad()
_demo_plan_persistence()
_demo_tool_handler()
print("\n" + "=" * 60)
print("Cleaning up demo files...")
_cleanup_demo()
Related skills
How it compares
Pick filesystem-context for file-backed overflow and handoffs; use memory-systems when semantic cross-session entity tracking is required.
FAQ
When should filesystem-context offload tool output to files?
filesystem-context recommends offloading tool outputs exceeding roughly 2000 tokens to scratch files, returning a compact summary and file path so agents grep or read line ranges on demand.
How do sub-agents share findings in filesystem-context?
filesystem-context routes each sub-agent to its own workspace directory under agents/, letting a coordinator read findings.md or test_results.txt directly instead of passing summarized message chains.