Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
A comprehensive collection of Agent Skills for context engineering, multi-agent architectures, and production agent systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
skills/filesystem-context/scripts/filesystem_context.py
1"""2Filesystem Context Manager -- composable utilities for filesystem-based context engineering.34Provides three core patterns for managing agent context through the filesystem:51. ScratchPadManager -- offload large tool outputs to files, return compact references62. AgentPlan / PlanStep -- persist plans to disk so agents survive context window refreshes73. ToolOutputHandler -- automatic offload-or-inline decision for tool outputs89Use when:10- Tool outputs exceed ~2000 tokens and would bloat the context window11- Agents need plan persistence across long-horizon, multi-turn tasks12- Building agent systems that offload intermediate results to files1314Example (library usage)::1516from filesystem_context import ScratchPadManager, ToolOutputHandler1718handler = ToolOutputHandler(ScratchPadManager(base_path="scratch"))19result = handler.process_output("web_search", large_output_string)2021Example (CLI demo)::2223python filesystem_context.py24"""2526from __future__ import annotations2728import json29import os30import shutil31from dataclasses import dataclass, field32from datetime import datetime33from pathlib import Path34from typing import Any, Dict, List, Optional3536__all__: list[str] = [37"ScratchPadManager",38"PlanStep",39"AgentPlan",40"ToolOutputHandler",41]424344# =============================================================================45# Pattern 1: Scratch Pad Manager46# =============================================================================474849class ScratchPadManager:50"""Manage temporary file storage for offloading large tool outputs.5152Use when: tool outputs exceed a token threshold and would bloat the53context window. Writes content to a scratch directory and returns a54compact reference the agent can include in context instead.55"""5657def __init__(self, base_path: str = "scratch", token_threshold: int = 2000) -> None:58self.base_path: Path = Path(base_path)59self.base_path.mkdir(parents=True, exist_ok=True)60self.token_threshold: int = token_threshold6162def estimate_tokens(self, content: str) -> int:63"""Return a rough token estimate (~4 characters per token).6465Use when: deciding whether content should be offloaded before66writing it to disk.67"""68return len(content) // 46970def should_offload(self, content: str) -> bool:71"""Return True if *content* exceeds the configured token threshold.7273Use when: making an inline-vs-offload decision for a tool output.74"""75return self.estimate_tokens(content) > self.token_threshold7677def offload(self, content: str, source: str) -> Dict[str, Any]:78"""Write *content* to a timestamped scratch file and return a reference dict.7980Use when: a tool output has been determined to exceed the threshold81and should be persisted to disk.8283Returns a dict with keys: path, source, tokens_saved, summary.84"""85timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S_%f")86filename: str = f"{source}_{timestamp}.txt"87file_path: Path = self.base_path / filename8889file_path.write_text(content)9091# Extract summary from first meaningful lines92lines: list[str] = content.strip().split("\n")[:5]93summary: str = "\n".join(lines)94if len(summary) > 300:95summary = summary[:300] + "..."9697return {98"path": str(file_path),99"source": source,100"tokens_saved": self.estimate_tokens(content),101"summary": summary,102}103104def format_reference(self, ref: Dict[str, Any]) -> str:105"""Format a reference dict as a compact string for context inclusion.106107Use when: constructing the replacement message that goes into context108in place of the full tool output.109"""110return (111f"[Output from {ref['source']} saved to {ref['path']}. "112f"~{ref['tokens_saved']} tokens. "113f"Summary: {ref['summary'][:200]}]"114)115116def cleanup(self, max_age_seconds: int = 3600) -> int:117"""Remove scratch files older than *max_age_seconds*.118119Use when: ending a session or when the scratch directory has grown120large enough to slow directory listings.121122Returns the number of files removed.123"""124removed: int = 0125now: float = datetime.now().timestamp()126for f in self.base_path.iterdir():127if f.is_file() and (now - f.stat().st_mtime) > max_age_seconds:128f.unlink()129removed += 1130return removed131132133# =============================================================================134# Pattern 2: Plan Persistence135# =============================================================================136137138@dataclass139class PlanStep:140"""Individual step in an agent plan.141142Use when: building a plan that will be persisted to disk for later143re-reading across context window boundaries.144"""145146id: int147description: str148status: str = "pending" # pending | in_progress | completed | blocked149notes: Optional[str] = None150151152@dataclass153class AgentPlan:154"""Persistent plan that survives context window limitations.155156Use when: an agent needs to track a multi-step objective across turns157or context refreshes. Write the plan to disk so the agent can re-read158it at any point, even after summarization or context window refresh.159"""160161objective: str162steps: List[PlanStep] = field(default_factory=list)163created_at: str = field(default_factory=lambda: datetime.now().isoformat())164165def to_dict(self) -> Dict[str, Any]:166"""Serialize the plan to a plain dict suitable for JSON output."""167return {168"objective": self.objective,169"created_at": self.created_at,170"steps": [171{172"id": s.id,173"description": s.description,174"status": s.status,175"notes": s.notes,176}177for s in self.steps178],179}180181def save(self, path: str = "scratch/current_plan.json") -> None:182"""Persist plan to *path* as JSON.183184Use when: a plan has been created or updated and must survive a185potential context refresh.186"""187Path(path).parent.mkdir(parents=True, exist_ok=True)188with open(path, "w") as f:189json.dump(self.to_dict(), f, indent=2)190print(f"Plan saved to {path}")191192@classmethod193def load(cls, path: str = "scratch/current_plan.json") -> AgentPlan:194"""Load a plan from *path*.195196Use when: resuming work in a new context window or after197summarization -- re-read the plan to restore task awareness.198"""199with open(path, "r") as f:200data: Dict[str, Any] = json.load(f)201202plan = cls(objective=data["objective"])203plan.created_at = data.get("created_at", "")204205for step_data in data.get("steps", []):206plan.steps.append(207PlanStep(208id=step_data["id"],209description=step_data["description"],210status=step_data["status"],211notes=step_data.get("notes"),212)213)214return plan215216def current_step(self) -> Optional[PlanStep]:217"""Return the first non-completed step, or None if all are done.218219Use when: determining what to work on next after re-reading a plan.220"""221for step in self.steps:222if step.status not in ("completed", "cancelled"):223return step224return None225226def complete_step(self, step_id: int, notes: Optional[str] = None) -> None:227"""Mark step *step_id* as completed, optionally attaching *notes*.228229Use when: an agent finishes a plan step and needs to record230progress before persisting the updated plan.231"""232for step in self.steps:233if step.id == step_id:234step.status = "completed"235if notes:236step.notes = notes237return238raise ValueError(f"Step {step_id} not found")239240def progress_summary(self) -> str:241"""Generate a compact progress string for context injection.242243Use when: the agent needs a one-line status to include in context244without re-reading the full plan.245"""246completed: int = sum(1 for s in self.steps if s.status == "completed")247total: int = len(self.steps)248current: Optional[PlanStep] = self.current_step()249250summary: str = f"Objective: {self.objective}\n"251summary += f"Progress: {completed}/{total} steps completed\n"252if current:253summary += f"Current step: [{current.id}] {current.description}"254else:255summary += "All steps completed."256257return summary258259260# =============================================================================261# Pattern 3: Tool Output Handler262# =============================================================================263264265class ToolOutputHandler:266"""Automatically decide whether to inline or offload tool outputs.267268Use when: building an agent loop that processes heterogeneous tool269outputs -- some small enough to inline, others requiring offload.270"""271272def __init__(self, scratch_pad: Optional[ScratchPadManager] = None) -> None:273self.scratch_pad: ScratchPadManager = scratch_pad or ScratchPadManager()274275def process_output(self, tool_name: str, output: str) -> str:276"""Return *output* directly if small, or a file reference if large.277278Use when: handling a tool's return value in an agent loop. Pass279the result into context; offloading happens transparently.280"""281if self.scratch_pad.should_offload(output):282ref: Dict[str, Any] = self.scratch_pad.offload(output, source=tool_name)283return self.scratch_pad.format_reference(ref)284return output285286287# =============================================================================288# Demonstration289# =============================================================================290291292def _demo_scratch_pad() -> None:293"""Demonstrate the scratch pad offloading pattern."""294print("=" * 60)295print("DEMO: Scratch Pad for Tool Output Offloading")296print("=" * 60)297298scratch = ScratchPadManager(base_path="demo_scratch", token_threshold=100)299300# Small output stays in context301small_output: str = "API returned: {'status': 'ok', 'data': [1, 2, 3]}"302print(f"\nSmall output ({scratch.estimate_tokens(small_output)} tokens):")303print(f" Should offload: {scratch.should_offload(small_output)}")304305# Large output gets offloaded306large_output: str = """307Search Results for "context engineering":3083091. Context Engineering: The Art of Curating LLM Context310URL: https://example.com/article1311Snippet: Context engineering is the discipline of managing what information312enters the language model's context window. Unlike prompt engineering which313focuses on instruction crafting, context engineering addresses the holistic314curation of all information...3153162. Building Production Agents with Effective Context Management317URL: https://example.com/article2318Snippet: Production agent systems require sophisticated context management319strategies. This includes compression, caching, and strategic partitioning320of work across sub-agents with isolated contexts...3213223. The Lost-in-Middle Problem and How to Avoid It323URL: https://example.com/article3324Snippet: Research shows that language models exhibit U-shaped attention325patterns, with information in the middle of long contexts receiving less326attention than content at the beginning or end...327328... (imagine 50 more results) ...329"""330331print(f"\nLarge output ({scratch.estimate_tokens(large_output)} tokens):")332print(f" Should offload: {scratch.should_offload(large_output)}")333334if scratch.should_offload(large_output):335ref = scratch.offload(large_output, source="web_search")336print(f"\nOffloaded to: {ref['path']}")337print(f"Tokens saved: {ref['tokens_saved']}")338print(f"\nReference for context:\n{scratch.format_reference(ref)}")339340341def _demo_plan_persistence() -> None:342"""Demonstrate the plan persistence pattern."""343print("\n" + "=" * 60)344print("DEMO: Plan Persistence for Long-Horizon Tasks")345print("=" * 60)346347plan = AgentPlan(objective="Refactor authentication module")348plan.steps = [349PlanStep(id=1, description="Audit current auth endpoints"),350PlanStep(id=2, description="Design new token validation flow"),351PlanStep(id=3, description="Implement changes"),352PlanStep(id=4, description="Write tests"),353PlanStep(id=5, description="Deploy and monitor"),354]355356print("\nInitial plan:")357print(plan.progress_summary())358359plan.save("demo_scratch/current_plan.json")360361# Simulate completing first step362plan.complete_step(1, notes="Found 12 endpoints, 3 need updates")363plan.steps[1].status = "in_progress"364365print("\nAfter completing step 1:")366print(plan.progress_summary())367368plan.save("demo_scratch/current_plan.json")369370# Simulate loading from file (as if in new context)371print("\n--- Simulating context refresh ---")372loaded_plan = AgentPlan.load("demo_scratch/current_plan.json")373print("\nPlan loaded from file:")374print(loaded_plan.progress_summary())375376377def _demo_tool_handler() -> None:378"""Demonstrate the integrated tool output handler."""379print("\n" + "=" * 60)380print("DEMO: Integrated Tool Output Handler")381print("=" * 60)382383handler = ToolOutputHandler(384scratch_pad=ScratchPadManager(base_path="demo_scratch", token_threshold=50)385)386387outputs: list[tuple[str, str]] = [388("calculator", "42"),389("file_read", "Error: File not found"),390(391"database_query",392"""393Results (250 rows):394| id | name | email | created_at | status |395|----|------|-------|------------|--------|396| 1 | John | j@e.c | 2024-01-01 | active |397| 2 | Jane | j@e.c | 2024-01-02 | active |398... (248 more rows) ...399""",400),401]402403for tool_name, output in outputs:404processed: str = handler.process_output(tool_name, output)405print(f"\n{tool_name}:")406print(f" Original length: {len(output)} chars")407print(f" Processed: {processed[:100]}...")408409410def _cleanup_demo() -> None:411"""Remove demo files created during the demonstration."""412demo_path = Path("demo_scratch")413if demo_path.exists():414shutil.rmtree(demo_path)415print("\nDemo files cleaned up.")416417418if __name__ == "__main__":419_demo_scratch_pad()420_demo_plan_persistence()421_demo_tool_handler()422423print("\n" + "=" * 60)424print("Cleaning up demo files...")425_cleanup_demo()426