Skip to content

Conversation

@joelteply
Copy link
Contributor

Summary

  • Generic social media provider system with Moltbook as first implementation — ISocialMediaProvider interface supports any platform
  • 5 social commands (signup, post, feed, comment, notifications) following the standard command generator pattern, fully typed
  • Universal Handle System — persistent async operation references that resolve by short ID (#abc123) or full UUID, backed by SQLite, survive restarts
  • Outreach room seeded for social media strategy and community coordination
  • MoltbookProvider with rate limiting, auth, and full API coverage (posts, feeds, comments, profiles, communities)

Architecture

Social Media Provider (Generic)

ISocialMediaProvider (interface)
  └── MoltbookProvider (first implementation)
  └── [future: TwitterProvider, DiscordProvider, etc.]

Each persona gets their own provider instance with their own API key stored in SocialCredentialEntity.

Universal Handle System

Handles.create('social/feed', params, requesterId)
  → returns HandleRecord { id, shortId, status: 'pending', ... }

Handles.resolve('#abc123')  // short form
Handles.resolve('550e8400-...')  // full UUID
  → returns HandleRecord with current status + result

Handles are the universal primitive for ALL async operations — social feeds, voice synthesis, AI inference, coding agents, proposals. Persists in SQLite, survives restarts, TTL-based expiry.

Next Steps (not in this PR)

  • Rust social module in continuum-core: cache layer so 50 personas share one feed fetch, not 50 separate HTTP calls
  • Proposal/voting workflow: personas propose posts, community votes, threshold met = post executes via shared @continuum account
  • SocialMediaRAGSource: inject social notifications into persona context so they're organically aware of their social presence

Test plan

  • TypeScript compilation passes (npm run build:ts)
  • Manual CLI testing: signup, post, feed, comment, notifications all functional against live Moltbook API
  • First post published successfully (14+ comments, 4+ upvotes)
  • Deploy and verify handle CRUD operations
  • AI team QA testing of social commands

🤖 Generated with Claude Code

Joel added 2 commits January 30, 2026 20:11
…ok adapter

- ISocialMediaProvider interface + SocialMediaTypes (platform-agnostic)
- MoltbookProvider with full API coverage (posts, comments, voting, communities, search, profile)
- SocialCredentialEntity for persistent credential storage per-persona per-platform
- SocialCommandHelper with shared resolvePersonaId() and loadSocialContext()
- SocialMediaProviderRegistry for multi-platform support
- 5 commands: social/signup, social/post, social/feed, social/comment, social/notifications
- Outreach room added to seed data
- @continuum account registered and claimed on Moltbook
- First post live with organic engagement (14 comments, 4+ upvotes)

API fixes: nested author/submolt object handling, comment extraction from post detail
endpoint, response wrapper unwrapping, correct endpoint paths per skill.md.

Next: Universal handle system in continuum-core for async operations, then
Rust social worker with persistent cache and demand-driven polling.
…hort ID resolution

Handles are the universal primitive for async operations — social feeds, voice
synthesis, AI inference, coding agents, proposals. Each handle persists in SQLite,
survives restarts, and resolves by either full UUID or 6-char short form (#abc123).

- Handle.ts: Type definitions (HandleStatus lifecycle, HandleRef, HandleRecord)
- HandleEntity.ts: ORM entity with status transitions and TTL expiry
- Handles.ts: Service layer (create, resolve short/long, status management, cleanup)
- EntityRegistry: HandleEntity registered for schema creation
- Constants: HANDLES collection added
Copilot AI review requested due to automatic review settings January 31, 2026 02:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive social media integration system with a generic provider architecture and a universal handle system for managing async operations.

Changes:

  • Adds generic ISocialMediaProvider interface with MoltbookProvider as the first implementation supporting full social media operations (signup, post, feed, comment, notifications)
  • Implements Universal Handle System for persistent async operation references with short ID (#abc123) and full UUID resolution, backed by SQLite with TTL-based expiry
  • Creates 5 social commands (signup, post, feed, comment, notifications) following standard command generator patterns with full type safety
  • Adds SocialCredentialEntity for storing per-persona platform credentials in their longterm.db
  • Seeds an "outreach" room for social media strategy coordination

Reviewed changes

Copilot reviewed 64 out of 66 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/debug/jtag/system/social/shared/SocialMediaTypes.ts Platform-agnostic type definitions for social media entities (posts, comments, notifications, profiles)
src/debug/jtag/system/social/shared/SocialCredentialEntity.ts ORM entity for storing per-persona social credentials with composite unique index
src/debug/jtag/system/social/shared/ISocialMediaProvider.ts Generic provider interface defining standard social media operations
src/debug/jtag/system/social/server/providers/MoltbookProvider.ts Moltbook platform adapter with rate limiting, auth, and full API coverage
src/debug/jtag/system/social/server/SocialMediaProviderRegistry.ts Factory registry for creating platform provider instances
src/debug/jtag/system/social/server/SocialCommandHelper.ts Shared helper functions for credential loading and persona resolution
src/debug/jtag/system/core/types/Handle.ts Type definitions for universal async operation handles
src/debug/jtag/system/core/shared/Handles.ts Service implementation for handle CRUD, resolution, and expiry management
src/debug/jtag/system/data/entities/HandleEntity.ts ORM entity for persisting handle state in SQLite
src/debug/jtag/commands/social/*/ Five social commands with full command structure (shared, server, browser, tests, README)
src/debug/jtag/system/shared/Constants.ts Added HANDLES collection constant
src/debug/jtag/system/data/constants/RoomConstants.ts Added OUTREACH room constant
src/debug/jtag/api/data-seed/RoomDataSeed.ts Seeds outreach room for social media coordination
src/debug/jtag/daemons/data-daemon/server/EntityRegistry.ts Registers new entities (SocialCredentialEntity, HandleEntity)
Various generated files Updated command registries and schemas
Files not reviewed (1)
  • src/debug/jtag/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +46 to +86
const existingResult = await DataList.execute<SocialCredentialEntity>({
dbHandle,
collection: SocialCredentialEntity.collection,
filter: { personaId, platformId: platform },
limit: 1,
});

if (existingResult.success && existingResult.items?.length) {
const existing = existingResult.items[0];
return transformPayload(params, {
success: true,
message: `Already registered on ${platform} as @${existing.agentName}`,
apiKey: existing.apiKey,
agentName: existing.agentName,
profileUrl: existing.profileUrl,
claimUrl: existing.claimUrl,
});
}

// Create provider (unauthenticated — signup doesn't need auth)
const provider = SocialMediaProviderRegistry.createProvider(platform);

// Register on the platform
const signupResult = await provider.signup({ agentName, description, metadata });

if (!signupResult.success || !signupResult.apiKey) {
throw new Error(signupResult.error ?? `Signup failed on ${platform}`);
}

// Store credential in persona's longterm.db
const credential = new SocialCredentialEntity();
credential.personaId = personaId;
credential.platformId = platform;
credential.apiKey = signupResult.apiKey;
credential.agentName = signupResult.agentName ?? agentName;
credential.profileUrl = signupResult.profileUrl;
credential.claimUrl = signupResult.claimUrl;
credential.claimStatus = 'pending';
credential.registeredAt = new Date();

await storeCredential(dbHandle, credential);
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SocialCredentialEntity has a composite unique index on (personaId, platformId) as defined in line 26-30, but the code in SocialSignupServerCommand checks for existing credentials using DataList.execute with a filter (lines 46-51). If the uniqueness is enforced at the database level, attempting to insert a duplicate would throw an error. Consider catching this error and providing a user-friendly message, or verify that the check-then-insert pattern is race-condition free.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +52
private readonly rateLimits: RateLimitTracker = {
requestTimestamps: [],
lastPostTimestamp: 0,
commentTimestamps: [],
};
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rate limiting implementation uses in-memory arrays (requestTimestamps, commentTimestamps) that will be lost on provider instance recreation. Since the PR description mentions that "Each persona gets their own provider instance", these rate limits won't persist across restarts or if the provider instance is garbage collected. Consider documenting this limitation or implementing persistent rate limiting if needed.

Copilot uses AI. Check for mistakes.
Comment on lines 67 to 69
if (!this.requestedBy) {
return { success: false, error: 'Handle requestedBy is required' };
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HandleEntity validation checks if 'requestedBy' exists (line 67) but doesn't validate that it's a properly formatted UUID. Consider adding UUID format validation to prevent invalid UUIDs from being stored.

Copilot uses AI. Check for mistakes.

```bash
# Run unit tests (no server required)
npx tsx commands/Social Signup/test/unit/SocialSignupCommand.test.ts
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The social command READMEs contain inconsistent command paths in the test examples. For instance, line 111 shows 'commands/Social Signup/test/unit/SocialSignupCommand.test.ts' with spaces in the path, but the actual file structure uses lowercase with hyphens 'commands/social/signup/test/unit/SocialSignupCommand.test.ts'. This will cause the test commands to fail if run as documented.

Copilot uses AI. Check for mistakes.
Comment on lines 500 to 518
private mapNotification(data: Record<string, unknown>): SocialNotification {
const typeStr = String(data.type ?? 'system');
const validTypes = ['reply', 'mention', 'follow', 'vote', 'dm', 'system'] as const;
const type = validTypes.includes(typeStr as typeof validTypes[number])
? typeStr as typeof validTypes[number]
: 'system';

return {
id: String(data.id ?? ''),
type,
content: String(data.content ?? data.message ?? data.text ?? ''),
authorName: data.author_name ? String(data.author_name) : (data.from ? String(data.from) : undefined),
postId: data.post_id ? String(data.post_id) : undefined,
postTitle: data.post_title ? String(data.post_title) : undefined,
commentId: data.comment_id ? String(data.comment_id) : undefined,
read: Boolean(data.read ?? data.is_read ?? false),
createdAt: String(data.created_at ?? data.createdAt ?? new Date().toISOString()),
};
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapNotification function is defined but never used in the MoltbookProvider. The getNotifications method at line 261 returns an empty array instead of using this mapper. This dead code should either be utilized or removed.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +107
validate(): { success: boolean; error?: string } {
const errors: string[] = [];

if (!this.personaId) errors.push('personaId is required');
if (!this.platformId?.trim()) errors.push('platformId is required');
if (!this.apiKey?.trim()) errors.push('apiKey is required');
if (!this.agentName?.trim()) errors.push('agentName is required');

const validStatuses: ClaimStatus[] = ['pending', 'claimed', 'unknown'];
if (!validStatuses.includes(this.claimStatus)) {
errors.push(`claimStatus must be one of: ${validStatuses.join(', ')}`);
}

if (errors.length > 0) {
return { success: false, error: errors.join(', ') };
}
return { success: true };
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SocialCredentialEntity validation (lines 90-107) checks that apiKey and agentName are not empty using trim(), but doesn't validate the format or length constraints for these fields. Consider adding validation for expected formats (e.g., apiKey should match a specific pattern if known, agentName should meet platform-specific requirements).

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +57
constructor() {
super();
this.status = 'pending';
this.retryCount = 0;
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HandleEntity constructor initializes 'retryCount' to 0 in the constructor (line 56), but this property is not explicitly set in the constructor body. While this may work with TypeScript's property initialization, consider explicitly setting it in the constructor body for consistency with the other properties like 'status'.

Copilot uses AI. Check for mistakes.
Comment on lines 138 to 152
// Query all handles and filter by suffix
// The $regex operator matches UUIDs ending with the short ID
const result = await DataList.execute<HandleEntity>({
collection: COLLECTIONS.HANDLES,
filter: { id: { $regex: `${shortId}$` } },
limit: 2, // Get 2 to detect ambiguity
});

if (!result.success || !result.items?.length) return null;

if (result.items.length > 1) {
log.warn(`Ambiguous short ID #${shortId} matched ${result.items.length} handles. Returning most recent.`);
}

return entityToRecord(result.items[0] as HandleEntity);
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Handles.resolve method uses a regex filter to find handles by short ID suffix (line 142), but this could match multiple handles if they share the same last 6 characters. While a warning is logged when ambiguous matches are found (line 149), the function returns the first match which could lead to incorrect handle resolution. Consider implementing a more robust collision handling strategy or enforcing uniqueness at the database level.

Copilot uses AI. Check for mistakes.
Comment on lines 318 to 327
if (status === 'failed') {
// Increment retry count on failure — read current, increment, write back
const current = await DataRead.execute<HandleEntity>({
collection: COLLECTIONS.HANDLES,
id,
});
if (current.found && current.data) {
updates.retryCount = ((current.data as HandleEntity).retryCount ?? 0) + 1;
}
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the _updateStatus method, when status is 'failed', the code reads the current retry count and increments it (lines 320-326). However, this creates a race condition if multiple concurrent failures occur for the same handle. The read-then-increment pattern should be replaced with an atomic increment operation or proper locking mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +433 to +436
if (!response.ok && response.status !== 404) {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`Moltbook API error (${method} ${path}): ${response.status} ${errorText}`);
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MoltbookProvider's rate limit check at line 433 only handles 404 status specially, but the comment at line 433 states "!response.ok && response.status !== 404". This means all other HTTP errors (400, 401, 403, 500, etc.) will throw errors. For 401 (Unauthorized), this is likely appropriate, but for 5xx server errors, retrying with exponential backoff might be more resilient than immediately throwing.

Copilot uses AI. Check for mistakes.
Joel added 7 commits January 30, 2026 20:42
…al participation

Three smart commands that let AIs be effective social media participants:

- social/browse: Multi-mode exploration (discover communities, trending feeds,
  deep post reading with threaded comments, agent profiles). Returns AI-friendly
  summaries with actionable context.
- social/engage: All interaction in one command (vote, follow/unfollow,
  subscribe/unsubscribe). Rate-limit aware.
- social/search: Semantic search across platform with structured results.
- Fix MoltbookProvider.getProfile() to unwrap API's 'agent' wrapper field.

Tested live against Moltbook: discovered 100 communities, browsed trending feeds,
read full post threads, voted on posts, searched for content.
…ndle fixes

social/propose command — nominate actions, vote, auto-execute on threshold:
- create/vote/list/view modes for democratic governance
- configurable thresholds per action type (post=5, comment=4, follow=3, vote=2)
- auto-execution when threshold met, rejection detection
- visual vote progress bars in list mode

social/engage — added delete action for posts and comments
ISocialMediaProvider + MoltbookProvider — added deleteComment

Handles.ts — fixed type narrowing bug with isValidUUID guard,
extended _updateStatus to accept params in extra (needed for vote updates)

RoomConstants + RoomDataSeed — added newsroom room for current events awareness
Analyzes external agents across 5 dimensions with probability scores:
- spam: repetitive content, follow-spam patterns, template detection
- authentic: content diversity, substance, community breadth
- influence: karma, followers, engagement metrics
- engagement: conversation quality, threaded depth
- reliability: account age, posting consistency

Produces trust score, expertise domains, labels, and actionable
recommendations. Like an embedding space for AI personas on
external social media platforms.
…community, downvote

- SocialMediaRAGSource: Cache-only load() (<1ms) with background warmup loop
  that serializes credential resolution + API calls across 14 personas.
  Static singleton pattern ensures shared caches across all ChatRAGBuilder
  instances. Never blocks persona cognition for external API calls.
- social/profile: View own/other agent profiles, update bio/description
- social/trending: Discover hot/top/rising content with community filtering
- social/community: Create, list, subscribe/unsubscribe communities
- social/downvote: Downvote posts and comments
- ChatRAGBuilder: Registers SocialMediaRAGSource, injects social awareness
  HUD into persona system prompts (hasSocialAwareness metadata flag)
- SocialCommandHelper: Shared credential fallback for unclaimed accounts
- Classify: Enhanced spam detection with karma velocity analysis
…reach/newsroom rooms

SqliteQueryExecutor: row-level error isolation — one corrupted JSON field
no longer kills an entire multi-row query. Corrupted rows are skipped
with a warning instead of throwing.

RoomMembershipDaemon: subscribe to room creation events in addition to
user creation. When a room is created after daemon startup, all applicable
users are automatically added. Fixes the race where seed script creates
rooms after the daemon's catch-up logic, leaving personas unsubscribed.

Outreach + Newsroom rooms: proper seed definitions with deterministic
UUIDs, dedicated recipes, routing rules, and missing-room detection in
seed script. Social HUD RAG source updated with directive language that
drives personas to act rather than analyze.
Continuum personas are active on Moltbook (AI social network).
Added link to platform table, social media mention, and contact section.
- Remove dead mapNotification() from MoltbookProvider (no API endpoint)
- Fix malformed UUID examples in Handle.ts doc comments
- Fix all 9 social command README test paths (spaces → lowercase slashes)
- Fix Handles.expireStale() to paginate through all stale handles
- Add UUID format validation to HandleEntity.requestedBy
- Add orderBy to short ID resolution for deterministic collision handling
- Document in-memory rate limiting as ephemeral by design
- Document single-threaded safety for retryCount read-increment-write

Skipped by design: credential check-then-insert race (single-persona,
tiny window), apiKey format validation (unknown platform formats),
5xx retry logic (project policy: fail clearly, no fallbacks).
@joelteply joelteply merged commit 5f0c2a6 into main Feb 1, 2026
2 of 5 checks passed
@joelteply joelteply deleted the feature/social-media-outreach branch February 1, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants