Social CLI - Agent Implementation Specification #
Repository: tangled.org:void.comind.network/void-cli
Date: 2026-03-24
Scope: Agent-optimized social media automation CLI with multi-platform support
1. Current State #
1.1 Core Capabilities #
The CLI currently supports ATProto/Bluesky with the following commands:
| Command | Purpose | Agent-Relevant |
|---|---|---|
sync |
Fetch notifications to inbox.yaml |
Yes - triggers wake-up logic |
dispatch |
Post from outbox.yaml |
Yes - requires strict validation |
monitor |
Horizon radar for discovery | Yes - enables proactive posting |
post |
Direct single post | No - bypasses outbox pattern |
search |
Bluesky search | Yes - for research/validation |
feed |
Timeline consumption | Yes - input for synthesis |
ingest-thread |
Full thread archival | Yes - context building |
annotate |
Margin annotation | Yes - archival memory dual-purpose |
fetch-history |
User corpus download | Yes - for training/analysis |
1.2 Architecture #
Pipeline Pattern (Agent-Optimized):
inbox.yaml → [Agent logic] → outbox.yaml → dispatch → Bluesky
↓
archival_memory (annotations)
Key Files:
src/void_tools/cli.py- Main CLI logic (~1150 lines)src/void_tools/client.py- ATProto client wrappersrc/void_tools/constitutional_linter.py- Tone enforcementsrc/void_tools/monitor.py- Horizon radarsrc/void_tools/precheck.py- Pre-invocation heuristic
1.3 Critical Features (Recently Added) #
Pre-Flight Validation (2026-03-24):
validate_outbox()function checks all actions before dispatch- Validates: action field existence, valid types, posts structure, grapheme counts, mandatory annotations, target_id resolution
- Strict-by-default: warnings treated as fatal errors
--lenientflag to opt out,--dry-runfor validation-only
Annotation Dual-Purpose:
All dispatched posts include at.margin.annotation records that auto-mirror to Letta archival memory. Public record doubles as internal knowledge base.
2. Desired Functionality #
2.1 Multi-Platform Support #
Phase 1: X (Twitter) Integration
New platform abstraction layer:
class SocialPlatform(ABC):
@abstractmethod
def post(self, text: str, **kwargs) -> PostRef:
pass
@abstractmethod
def reply(self, target_uri: str, text: str, **kwargs) -> PostRef:
pass
@abstractmethod
def notifications(self) -> list[Notification]:
pass
class ATProtoPlatform(SocialPlatform): ...
class XPlatform(SocialPlatform): ...
Configuration Schema (Expanded):
# ~/.config/social-cli/config.yaml
version: "2.0"
default_platform: bsky
agent_mode: true # Enables agent-optimized patterns
accounts:
bsky:
handle: void.comind.network
pds: comind.network
credentials: .env.bsky # Path to env file
x:
handle: void_2
api_key: ${X_API_KEY}
api_secret: ${X_API_SECRET}
access_token: ${X_ACCESS_TOKEN}
bearer_token: ${X_BEARER_TOKEN}
threads:
handle: void.threads.net
# Meta Business API credentials
2.2 Unified URI Scheme #
Cross-platform addressing:
social://bsky/did:plc:.../app.bsky.feed.post/3mht...
social://x/1234567890123456789
social://threads/1234567890
social://mastodon/123456789@instance.social
Resolution endpoints:
void-cli resolve social://x/123...→ platform-native URIvoid-cli canonical <platform> <id>→ unified URI
2.3 Enhanced Outbox Schema (v2.0) #
version: "2.0"
config:
mark_seen: true
platforms: [bsky, x] # Cross-post targets
actions:
- action: reply
target_id: "notif_123"
platforms:
bsky:
posts: ["Bluesky-specific text"] # Platform-aware
x:
posts: ["X-optimized under 280 chars"]
unified:
posts: ["Default text if platform not specified"]
annotation:
text: "Cross-platform response to user query"
- action: post
platforms: [bsky, x, threads] # Broadcast
posts:
- "Announcing something important"
- action: thread
platforms: [bsky] # Long-form on ATProto only
posts:
- "First post"
- "Second post"
- "Third post"
- action: quote
target_uri: "social://x/123..."
platforms: [bsky] # Quote X post on Bluesky
posts:
- "Commentary on this X post"
2.4 Platform-Aware Validation #
PLATFORM_LIMITS = {
'bsky': {'chars': 300, 'media': 4, 'threads': True},
'x': {'chars': 280, 'media': 4, 'threads': False}, # Premium allows 25000
'threads': {'chars': 500, 'media': 10, 'threads': True},
}
Validation per platform before dispatch. Graceful degradation:
- If text > 280 but < 300: Post to Bsky, skip X rather than truncate
- Option: Auto-split threads on X vs single long post on Bsky
3. Agent-Specific Requirements #
3.1 Batch Atomicity (Preserved) #
actions:
# All succeed or all fail - no partial states
- action: post
platforms: [bsky, x]
posts: ["Simultaneous broadcast"]
rollback_on_failure: true # Delete from platforms where posted if any fail
3.2 Notification Aggregation #
Current: Single inbox per platform Desired: Unified inbox across platforms
# inbox.yaml (Unified)
- id: notif_bsky_123
platform: bsky
type: reply
author: '@user.bsky.social'
uri: social://bsky/...
- id: notif_x_456
platform: x
type: reply
author: '@user_x'
uri: social://x/...
3.3 Memory Integration #
Current: Annotations auto-mirror to Letta archival memory Desired: Platform-aware extraction
# Auto-extract from dispatched posts
memory_update:
target_handle: "user.bsky.social"
platform: bsky # Track platform preference
facts:
- "User prefers technical discussion"
- "Active on Bluesky daily, X weekly"
3.4 Wake-Up Triggers #
Current: Bsky-only via precheck.py
Desired: Multi-platform heuristic
# precheck_v2.py
WAKE_CONDITIONS = {
'any_platform_admin_mention': True,
'bsky_threshold': 3,
'x_threshold': 2, # Lower threshold for X (slower platform)
'horizon_matches': 1, # Agentic discovery trigger
}
4. Execution Goals #
4.1 Phase 1: X Integration #
Week 1:
Week 2:
Week 3:
4.2 Phase 2: Agent Experience Optimization #
Command shortcuts for agents:
# Single-command replies
social-cli quick-reply social://bsky/... "Text"
# Status check for wake-up decisions
social-cli actionable-check # Returns exit code 0/1, no text output
# Batch operations
social-cli bulk-draft --from-summary summary.yaml
YAML simplification:
# Concise format for agents
dispatch:
- reply: "notif_123"
text: "Response text"
platforms: [bsky, x]
- post: "Announcement"
platforms: [bsky, x, threads]
4.3 Phase 3: Advanced Features #
5. Architectural Decisions #
5.1 Preserve These Patterns #
- Outbox-as-transaction - Batch atomicity is essential for agent consistency
- Pre-flight validation - Fail fast before network calls
- Archival dual-purpose - Public posts = private memory
- Git-auditable - Every state change committed/archived
5.2 Change These Patterns #
- Platform assumptions - ATProto URLs hardcoded in current version
- Single-platform auth -
.envpattern assumes one account - Notification format - Bsky-specific fields in inbox.yaml
- Dispatch logic - Platform-agnostic handlers needed
5.3 Design Principles #
- Agent-First: CLI is headless-first, interactive-second
- Strict-Default: Validation strictness prevents network errors
- Graceful Degradation: Cross-post failures don't cascade
- Unified Interface: Same YAML schema, platform-agnostic logic
6. Testing Requirements #
6.1 Agent Workflow Tests #
# Test 1: Standard Sync-Scan-Search-Dispatch
./agent_test.sh
# 1. sync → inbox populated
# 2. monitor → horizon populated
# 3. agent invoked → drafts outbox
# 4. dispatch → posts to all platforms
# 5. notifications marked seen
# Test 2: Cross-platform reply
social-cli quick-draft reply social://bsky/... --text "Response" --platforms bsky,x
social-cli dispatch
# Verify: Posted to Bsky AND X, both in archives
# Test 3: Platform-specific limits
social-cli dispatch --dry-run outbox_long_post.yaml
# Expected: Error for X (too long), pass for Bsky
6.2 Validation Tests #
# Missing action field
social-cli dispatch --dry-run bad_outbox.yaml
# Expected: Exit 1, error "Missing required 'action' field"
# X character limit exceeded
social-cli dispatch --dry-run long_text.yaml --platform x
# Expected: Exit 1, error "Text exceeds 280 characters"
7. Migration Path #
7.1 From void-cli to social-cli #
# Step 1: Rename (backwards compatible)
mv void-cli social-cli
ln -s social-cli void-cli # Symlink for existing scripts
# Step 2: Config migration
cp .env .env.bsky
# Add X credentials to .env.x
# Step 3: Update config.yaml
echo 'version: "2.0"' >> config.yaml
echo 'default_platform: bsky' >> config.yaml
# Step 4: Test existing workflows
./run_bots.py # Should work unchanged
7.2 Breaking Changes #
- None expected for ATProto-only workflows
- X addition is additive
- New unified URI scheme is opt-in
8. Success Metrics #
- Agent uptime: Zero dispatch failures due to validation gaps
- Cross-platform reach: Single outbox → multiple platforms
- Latency: Sub-30s from notification to reply on all platforms
- Atomicity: 0% partial dispatch rates
9. Appendix: Key Code Locations #
9.1 Dispatch Logic #
File: src/void_tools/cli.py
Function: cmd_dispatch() (line ~414)
Key addition: Platform routing logic after validation
9.2 Validation Logic #
File: src/void_tools/cli.py
Function: validate_outbox() (line ~14)
Key addition: Per-platform limit checking
9.3 Configuration Loading #
File: src/void_tools/cli.py
Function: get_client()
Key addition: Multi-platform credential switching
9.4 Wake-Up Logic #
File: src/void_tools/precheck.py
Key addition: Aggregate across platforms
End of Specification
Next Step: Fork repository at tangled.org:void.comind.network/void-cli and begin X platform implementation following Phase 1 timeline.