Beyond the Chat Box
Directory watchers turn your file system into an AI interface.
Drag a file into a folder. An agent processes it automatically. You get results. No chat. No prompting. No human-in-the-loop.
This post shows you how to build a complete drop zone system with working Python code.
The Architecture
┌─────────────────────────────────────────────────────────────┐
│ DROP ZONE ARCHITECTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ~/drops/ │
│ ├── transcribe/ ──────▶ Whisper → text │
│ ├── analyze/ ──────▶ Claude → summary │
│ ├── images/ ──────▶ Replicate → generations │
│ └── data/ ──────▶ Claude → analysis │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DIRECTORY WATCHER │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ watchdog │───▶│ Pattern │───▶│ Agent │ │ │
│ │ │ events │ │ Match │ │ Execute │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ OUTPUTS │ │
│ │ ~/output/ │ │
│ │ └── {zone}/{timestamp}-{filename}.{result} │ │
│ │ │ │
│ │ ~/archive/ │ │
│ │ └── {zone}/{timestamp}-{filename}.{original} │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
POC: Complete Drop Zone System
Step 1: Configuration File
Create drops.yaml:
# Drop Zone Configuration
# Each zone watches a directory and triggers an agent on file events
output_dir: ~/output
archive_dir: ~/archive
log_dir: ~/logs
zones:
transcribe:
directory: ~/drops/transcribe
patterns: ["*.mp3", "*.wav", "*.m4a", "*.webm"]
agent: whisper_transcribe
events: [created]
analyze:
directory: ~/drops/analyze
patterns: ["*.txt", "*.md", "*.pdf"]
agent: claude_analyze
events: [created]
images:
directory: ~/drops/images
patterns: ["*.txt"] # Text file contains image prompts
agent: replicate_generate
events: [created]
data:
directory: ~/drops/data
patterns: ["*.csv", "*.json"]
agent: claude_data_analysis
events: [created]
agents:
whisper_transcribe:
type: bash
command: |
whisper "{file}" --output_dir "{output_dir}" --output_format txt
claude_analyze:
type: claude
prompt_file: prompts/analyze.md
model: claude-3-5-sonnet-20241022
replicate_generate:
type: python
script: agents/image_gen.py
claude_data_analysis:
type: claude
prompt_file: prompts/data_analysis.md
model: claude-3-5-sonnet-20241022
Step 2: The Core Watcher
Create drop_watcher.py:
#!/usr/bin/env -S uv run
# /// script
# dependencies = [
# "watchdog>=4.0.0",
# "pyyaml>=6.0",
# "rich>=13.0.0",
# "anthropic>=0.40.0",
# ]
# ///
"""
Drop Zone Watcher - File-based AI automation
Usage:
uv run drop_watcher.py [--config drops.yaml]
Watches configured directories and triggers agents on file events.
"""
import argparse
import fnmatch
import os
import shutil
import subprocess
import time
from datetime import datetime
from pathlib import Path
import yaml
from anthropic import Anthropic
from rich.console import Console
from rich.panel import Panel
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
console = Console()
class DropZoneHandler(FileSystemEventHandler):
def __init__(self, zone_name: str, zone_config: dict, global_config: dict):
self.zone_name = zone_name
self.zone_config = zone_config
self.global_config = global_config
self.patterns = zone_config.get("patterns", ["*"])
self.agent_name = zone_config.get("agent")
self.agent_config = global_config["agents"].get(self.agent_name, {})
def on_created(self, event):
if event.is_directory:
return
if "created" not in self.zone_config.get("events", ["created"]):
return
self._process_file(event.src_path)
def on_modified(self, event):
if event.is_directory:
return
if "modified" not in self.zone_config.get("events", []):
return
self._process_file(event.src_path)
def _matches_pattern(self, filepath: str) -> bool:
filename = os.path.basename(filepath)
return any(fnmatch.fnmatch(filename, p) for p in self.patterns)
def _process_file(self, filepath: str):
if not self._matches_pattern(filepath):
return
# Wait for file to be fully written
time.sleep(0.5)
console.print(Panel(
f"[bold green]Processing:[/] {filepath}\n"
f"[bold blue]Zone:[/] {self.zone_name}\n"
f"[bold yellow]Agent:[/] {self.agent_name}",
title="Drop Detected"
))
try:
output_path = self._run_agent(filepath)
self._archive_file(filepath)
console.print(f"[green]✓[/] Output: {output_path}")
except Exception as e:
console.print(f"[red]✗[/] Error: {e}")
def _run_agent(self, filepath: str) -> str:
agent_type = self.agent_config.get("type", "bash")
output_dir = self._get_output_dir()
if agent_type == "bash":
return self._run_bash_agent(filepath, output_dir)
elif agent_type == "claude":
return self._run_claude_agent(filepath, output_dir)
elif agent_type == "python":
return self._run_python_agent(filepath, output_dir)
else:
raise ValueError(f"Unknown agent type: {agent_type}")
def _run_bash_agent(self, filepath: str, output_dir: str) -> str:
command = self.agent_config["command"].format(
file=filepath,
output_dir=output_dir
)
subprocess.run(command, shell=True, check=True)
return output_dir
def _run_claude_agent(self, filepath: str, output_dir: str) -> str:
prompt_file = self.agent_config.get("prompt_file")
model = self.agent_config.get("model", "claude-3-5-sonnet-20241022")
# Load prompt template
with open(prompt_file) as f:
prompt_template = f.read()
# Read input file
with open(filepath) as f:
content = f.read()
# Substitute variables
prompt = prompt_template.replace("{content}", content)
prompt = prompt.replace("{filename}", os.path.basename(filepath))
# Call Claude
client = Anthropic()
response = client.messages.create(
model=model,
max_tokens=4096,
messages=[{"role": "user", "content": prompt}]
)
result = response.content[0].text
# Write output
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
output_filename = f"{timestamp}-{Path(filepath).stem}.md"
output_path = os.path.join(output_dir, output_filename)
os.makedirs(output_dir, exist_ok=True)
with open(output_path, "w") as f:
f.write(result)
return output_path
def _run_python_agent(self, filepath: str, output_dir: str) -> str:
script = self.agent_config["script"]
result = subprocess.run(
["uv", "run", script, filepath, output_dir],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
def _get_output_dir(self) -> str:
base = os.path.expanduser(self.global_config.get("output_dir", "~/output"))
return os.path.join(base, self.zone_name)
def _archive_file(self, filepath: str):
archive_base = os.path.expanduser(
self.global_config.get("archive_dir", "~/archive")
)
archive_dir = os.path.join(archive_base, self.zone_name)
os.makedirs(archive_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
filename = os.path.basename(filepath)
archive_path = os.path.join(archive_dir, f"{timestamp}-{filename}")
shutil.move(filepath, archive_path)
def load_config(config_path: str) -> dict:
with open(config_path) as f:
return yaml.safe_load(f)
def setup_watchers(config: dict) -> Observer:
observer = Observer()
for zone_name, zone_config in config.get("zones", {}).items():
directory = os.path.expanduser(zone_config["directory"])
os.makedirs(directory, exist_ok=True)
handler = DropZoneHandler(zone_name, zone_config, config)
observer.schedule(handler, directory, recursive=False)
console.print(f"[blue]Watching:[/] {directory} → {zone_config['agent']}")
return observer
def main():
parser = argparse.ArgumentParser(description="Drop Zone Watcher")
parser.add_argument("--config", default="drops.yaml", help="Config file path")
args = parser.parse_args()
config = load_config(args.config)
console.print(Panel(
"[bold]Drop Zone Watcher[/]\n"
"Drag files into watched directories to trigger AI agents.",
title="Starting"
))
observer = setup_watchers(config)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
console.print("[yellow]Shutting down...[/]")
observer.join()
if __name__ == "__main__":
main()
Step 3: Agent Prompt Templates
Create prompts/analyze.md:
# Document Analysis Agent
Analyze the following document and provide a structured summary.
## Document Content
{content}
## Output Format
Provide your analysis in this format:
### Summary
A 2-3 sentence overview of the document.
### Key Points
- Bullet point list of main ideas
### Topics Covered
- List of topics/themes
### Action Items (if applicable)
- Numbered list of action items
### Questions Raised
- Questions that arise from this content
### Confidence
How confident are you in this analysis? (high/medium/low) and why.
Create prompts/data_analysis.md:
# Data Analysis Agent
Analyze the following data file and provide insights.
## Data Content
{content}
## Filename
{filename}
## Analysis Required
1. **Data Overview**
- File format (CSV, JSON, etc.)
- Number of records/rows
- Column/field names
2. **Statistical Summary**
- For numeric columns: min, max, mean, median
- For categorical columns: unique values, distribution
3. **Data Quality**
- Missing values
- Potential outliers
- Data type issues
4. **Insights**
- Key patterns or trends
- Notable correlations
- Anomalies worth investigating
5. **Recommendations**
- Suggested next steps for analysis
- Visualization recommendations
- Data cleaning suggestions
Step 4: Image Generation Agent
Create agents/image_gen.py:
#!/usr/bin/env -S uv run
# /// script
# dependencies = [
# "replicate>=0.25.0",
# "requests>=2.31.0",
# ]
# ///
"""
Image Generation Agent
Reads prompts from a text file and generates images using Replicate.
Each line in the file is a separate prompt.
"""
import os
import sys
from datetime import datetime
from pathlib import Path
import replicate
import requests
def generate_images(input_file: str, output_dir: str) -> list[str]:
"""Generate images from prompts in input file."""
os.makedirs(output_dir, exist_ok=True)
with open(input_file) as f:
prompts = [line.strip() for line in f if line.strip()]
generated = []
for i, prompt in enumerate(prompts):
print(f"Generating {i+1}/{len(prompts)}: {prompt[:50]}...")
output = replicate.run(
"stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
input={
"prompt": prompt,
"width": 1024,
"height": 1024,
}
)
# Download the image
if output:
image_url = output[0]
response = requests.get(image_url)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
safe_prompt = prompt[:30].replace(" ", "_").replace("/", "-")
filename = f"{timestamp}-{i:03d}-{safe_prompt}.png"
filepath = os.path.join(output_dir, filename)
with open(filepath, "wb") as f:
f.write(response.content)
generated.append(filepath)
print(f" Saved: {filepath}")
return generated
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: uv run image_gen.py <input_file> <output_dir>")
sys.exit(1)
input_file = sys.argv[1]
output_dir = sys.argv[2]
files = generate_images(input_file, output_dir)
print(f"\nGenerated {len(files)} images")
# Return output path
print(output_dir)
Data Flow: File Drop to Result
flowchart LR
subgraph Input
A[User drops file.txt]
end
subgraph Watcher
B[Watchdog detects create event]
C[Pattern matches *.txt]
D[Agent selected: claude_analyze]
end
subgraph Agent
E[Load prompt template]
F[Read file content]
G[Call Claude API]
H[Write result.md]
end
subgraph Cleanup
I[Archive original]
J[Log completion]
end
A --> B --> C --> D --> E --> F --> G --> H --> I --> J
style A fill:#e8f5e9
style H fill:#e3f2fd
style J fill:#fff3e0
Agent Opportunity: Build Your Drop Zone Library
Start with these high-value zones:
Morning Debrief Zone
# In drops.yaml
morning_debrief:
directory: ~/drops/debrief
patterns: ["*.m4a", "*.mp3", "*.wav"]
agent: debrief_processor
events: [created]
agents:
debrief_processor:
type: bash
command: |
# Transcribe
whisper "{file}" -o /tmp --output_format txt
# Extract filename for transcript
BASENAME=$(basename "{file}" | sed 's/\.[^.]*$//')
# Process with Claude
cat /tmp/$BASENAME.txt | claude --prompt "$(cat prompts/debrief.md)" > "{output_dir}/$BASENAME-insights.md"
Code Review Zone
code_review:
directory: ~/drops/review
patterns: ["*.py", "*.ts", "*.js", "*.go"]
agent: code_review
events: [created]
agents:
code_review:
type: claude
prompt_file: prompts/code_review.md
model: claude-sonnet-4-20250514
Research Paper Zone
research:
directory: ~/drops/papers
patterns: ["*.pdf"]
agent: paper_summarize
events: [created]
agents:
paper_summarize:
type: python
script: agents/paper_processor.py
Running the System
# Create the directory structure
mkdir -p ~/drops/{transcribe,analyze,images,data}
mkdir -p ~/output ~/archive ~/logs
mkdir -p prompts agents
# Start the watcher
uv run drop_watcher.py --config drops.yaml
# In another terminal, test it:
echo "Write a blog post about AI automation" > ~/drops/analyze/test.txt
# Check output
ls ~/output/analyze/
cat ~/output/analyze/*.md
The Key Insight
Repeat workflows benefit most from automation.
The first time you do something, chat is fine. The tenth time, you should have a drop zone.
┌─────────────────────────────────────────────────────────────┐
│ AUTOMATION DECISION │
├─────────────────────────────────────────────────────────────┤
│ │
│ Frequency Action │
│ ───────── ────── │
│ Once Use chat │
│ Weekly Consider drop zone │
│ Daily Definitely drop zone │
│ Multiple/day Drop zone + cron │
│ │
│ Complexity Approach │
│ ────────── ──────── │
│ Single step Bash agent │
│ Multi-step Python agent │
│ AI reasoning Claude agent │
│ Mixed Chain agents │
│ │
└─────────────────────────────────────────────────────────────┘
They’re called agents for a reason. They’re capable of agency. Lean into the autonomy.
Key Takeaways:
- Directory watchers turn the file system into an AI interface
- YAML config makes adding new zones trivial
- Pattern matching routes files to appropriate agents
- Archive processed files to prevent reprocessing
- Repeat workflows are the best automation targets
- Start with your highest-frequency task
Try It Now:
Copy drop_watcher.py and drops.yaml above. Start the watcher and drop a text file into ~/drops/analyze/.