File Protocol Skill
Overview
This skill provides guidance for implementing file-based communication protocols that enable multiple agents to coordinate through structured file reads and writes. This approach is particularly valuable for multi-agent systems where agents need to share state, signal events, and coordinate actions without direct API communication.
Core Concepts
File-Based Communication
File-based communication uses the filesystem as a message-passing and state-sharing medium:
Advantages:
- Simple and transparent (can inspect files manually)
- Language/process agnostic
- Built-in persistence (files survive process crashes)
- Easy debugging and logging
- No network configuration required
Challenges:
- Race conditions (simultaneous reads/writes)
- File locking coordination
- Atomicity concerns
- Performance (disk I/O overhead)
Best for: Multi-agent coordination, game state management, asynchronous workflows
Communication Patterns
Signal Files: Trigger-based coordination
- File presence or modification signals an event
- Example:
turn-signal.jsontriggers player agent spawn
State Files: Shared state management
- Central authority writes, multiple readers consume
- Example:
game-state.jsonmaintains authoritative state
Action Files: Request/response communication
- Agents write action requests, coordinator processes and responds
- Example: Player writes
player-actions/player-1.json, gamemaster reads and validates
Log Files: Append-only history
- Immutable record of events for debugging and analysis
- Example:
game-log.jsonrecords all moves
File Structure Design
Directory Organization
Organize files by purpose and access pattern:
games/<game-name>/
├── RULES.md # Static configuration (read-only)
├── state/ # Active game state (read-write)
│ ├── game-state.json # Authoritative state (GM writes, all read)
│ ├── turn-signal.json # Turn notifications (GM writes, hooks read)
│ ├── player-actions/ # Player decisions (players write, GM reads)
│ │ ├── player-1.json
│ │ └── player-2.json
│ └── .locks/ # Lock files for coordination
│ └── game-state.lock
├── logs/ # Completed games (append-only)
│ └── game-2024-01-27.json
└── traces/ # Detailed debugging (append-only)
└── game-2024-01-27.md
File Naming Conventions
Use consistent, descriptive names:
State files: Describe the content
game-state.json(notstate.json)turn-signal.json(notsignal.json)player-1-hand.json(notp1.json)
Action files: Include actor identifier
player-actions/player-1.jsonplayer-actions/player-2.json
Lock files: Match the protected resource
.locks/game-state.lock.locks/turn-signal.lock
Log files: Include timestamp
logs/game-2024-01-27-14-30-00.jsontraces/game-2024-01-27-14-30-00.md
JSON Schema Design
Principles
Include metadata: Every file should have context
{
"fileType": "game-state",
"version": "1.0",
"timestamp": "2024-01-27T14:30:00Z",
"gameId": "uno-game-42",
"data": { ... }
}
Use explicit types: Avoid ambiguous values
// Good
{"action": "play", "card": {"color": "Red", "value": "7"}}
// Bad
{"action": "p", "c": "R7"}
Include validation: Add fields for integrity checking
{
"turnNumber": 42,
"previousTurnHash": "abc123...",
"playerId": "player-1",
"action": { ... }
}
State File Schema
Authoritative game state maintained by gamemaster:
{
"fileType": "game-state",
"version": "1.0",
"timestamp": "2024-01-27T14:30:00Z",
"game": "UNO",
"gameId": "uno-game-42",
"turnNumber": 12,
"gameActive": true,
"players": [
{
"id": "player-1",
"cardCount": 5,
"score": 0,
"isActive": true
}
],
"currentPlayer": "player-1",
"direction": 1,
"deck": {
"remaining": 42,
"shuffled": true
},
"discardPile": [
{"color": "Red", "value": "7"}
]
}
Turn Signal Schema
Signals which player should act:
{
"fileType": "turn-signal",
"version": "1.0",
"timestamp": "2024-01-27T14:30:05Z",
"gameId": "uno-game-42",
"turnNumber": 12,
"currentPlayer": "player-1",
"availableActions": ["play", "draw"],
"visibleState": {
"discardTop": {"color": "Red", "value": "7"},
"opponentCardCounts": {
"player-2": 3,
"player-3": 6,
"player-4": 4
}
}
}
Action File Schema
Player decision output:
{
"fileType": "player-action",
"version": "1.0",
"timestamp": "2024-01-27T14:30:10Z",
"gameId": "uno-game-42",
"playerId": "player-1",
"turnNumber": 12,
"action": "play",
"card": {
"color": "Blue",
"value": "7"
},
"reasoning": "Changing to Blue to keep Red 7 as option",
"confidence": 0.85
}
See references/schema-reference.md for complete schema specifications and validation rules.
Atomic Operations
Read-Modify-Write Pattern
When updating state based on previous value:
- Acquire lock
while ! mkdir .locks/game-state.lock 2>/dev/null; do
sleep 0.1
done
- Read current state
const state = JSON.parse(await Read("games/uno/state/game-state.json"));
- Modify state
state.turnNumber += 1;
state.currentPlayer = "player-2";
- Write atomically
// Write to temp file first
await Write("games/uno/state/game-state.json.tmp", JSON.stringify(state, null, 2));
// Atomic rename
await Bash("mv games/uno/state/game-state.json.tmp games/uno/state/game-state.json");
- Release lock
rmdir .locks/game-state.lock
Write-Only Pattern
When writing new file (no read needed):
- Generate unique filename
const filename = `player-actions/player-${playerId}-${Date.now()}.json`;
- Write directly (no lock needed for new files)
await Write(`games/uno/state/${filename}`, JSON.stringify(action, null, 2));
Race Condition Handling
Common Scenarios
Scenario 1: Simultaneous state updates
- Problem: Two agents try to update game-state.json
- Solution: Use lock files, only one agent writes at a time
Scenario 2: Reading stale data
- Problem: Agent reads state before latest update completes
- Solution: Include version numbers or timestamps, retry on conflict
Scenario 3: Lost updates
- Problem: Second write overwrites first write
- Solution: Atomic write pattern with lock acquisition
See references/race-conditions.md for detailed scenarios and solutions.
File Locking Strategies
Directory-Based Locks
Most reliable cross-platform approach:
# Acquire lock (mkdir is atomic)
while ! mkdir .locks/resource.lock 2>/dev/null; do
sleep 0.1
# Timeout after 10 seconds
if [ $SECONDS -gt 10 ]; then
echo "Lock timeout"
exit 1
fi
done
# Critical section
# ... perform locked operations ...
# Release lock
rmdir .locks/resource.lock
PID-Based Locks
Track lock owner for debugging:
# Acquire with PID
mkdir .locks/resource.lock
echo $$ > .locks/resource.lock/owner
# Critical section
# ...
# Release
rm .locks/resource.lock/owner
rmdir .locks/resource.lock
Stale Lock Detection
Handle crashes that leave locks:
# Check lock age
if [ -d .locks/resource.lock ]; then
lock_age=$(($(date +%s) - $(stat -f%m .locks/resource.lock)))
if [ $lock_age -gt 30 ]; then
echo "Removing stale lock"
rmdir .locks/resource.lock
fi
fi
See references/locking-patterns.md for complete implementations.
Hook Integration
File-based communication integrates with hooks for event-driven coordination:
PostToolUse(Write) hook detects file changes:
{
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "prompt",
"prompt": "A file was written: {{tool_use.parameters.file_path}}. If this is a turn-signal or player-action file in games/*/state/, trigger appropriate agent response."
}]
}]
}
See hook-sync skill for detailed hook implementation.
Best Practices
File Design
✅ DO:
- Use descriptive, consistent naming
- Include metadata in every file
- Use JSON for structured data
- Version your schemas
- Add timestamps for debugging
❌ DON'T:
- Use cryptic abbreviations
- Mix different data types in one file
- Rely on file modification times only
- Change schemas without versioning
- Omit error context
Concurrency
✅ DO:
- Use locks for read-modify-write
- Implement timeout for lock acquisition
- Clean up stale locks
- Write to temp files then rename
- Handle lock contention gracefully
❌ DON'T:
- Skip locks for "fast" operations
- Hold locks longer than necessary
- Ignore lock acquisition failures
- Write directly to final file
- Assume operations are instant
Error Handling
✅ DO:
- Validate JSON schema on read
- Check file existence before reading
- Handle parse errors gracefully
- Log all file operations
- Provide clear error messages
❌ DON'T:
- Assume files always exist
- Skip validation
- Silently fail
- Leave incomplete files
- Ignore I/O errors
Debugging
File Inspection
Monitor file changes in real-time:
# Watch directory for changes
watch -n 0.5 'ls -lt games/uno/state/'
# Tail logs as they're written
tail -f games/uno/logs/game-latest.json
# View file with timestamps
stat -f "%Sm %N" -t "%Y-%m-%d %H:%M:%S" games/uno/state/*.json
Validation Tools
Validate JSON files:
# Check JSON syntax
jq empty games/uno/state/game-state.json
# Validate against schema
ajv validate -s schema.json -d games/uno/state/game-state.json
# Pretty-print for inspection
jq . games/uno/state/game-state.json
Common Issues
File not found: Check paths, ensure directory exists Parse error: Validate JSON syntax, check for partial writes Stale data: Verify atomic write pattern, check timestamps Lock contention: Review lock acquisition logic, add logging Race condition: Add locks, use atomic operations
Additional Resources
Reference Files
For detailed implementation guidance:
references/schema-reference.md- Complete JSON schemas and validationreferences/race-conditions.md- Detailed race condition scenarios and solutionsreferences/locking-patterns.md- Advanced locking implementations
Example Files
Working examples in examples/:
file-operations.sh- Shell scripts for atomic operationsstate-schemas.json- JSON schema definitionsvalidation-example.js- File validation implementation
Integration
This skill works together with:
- game-coordination: Agents that read/write these files
- hook-sync: Hooks that detect file changes and trigger agents
For complete multi-agent file-based coordination, use all three skills together.
Scan to join WeChat group