diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 34ac3b8..6463e2c 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -49,6 +49,7 @@ export default defineConfig({
{ text: 'Why Trotsky', link: '/guide/why' },
{ text: 'Features', link: '/guide/features' },
{ text: 'Code of Conduct', link: '/guide/code-of-conduct' },
+ { text: 'Architecture', link: '/guide/architecture' },
{ text: 'FAQ', link: '/guide/faq' },
]
},
diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md
new file mode 100644
index 0000000..68a3c7f
--- /dev/null
+++ b/docs/guide/architecture.md
@@ -0,0 +1,425 @@
+# Architecture
+
+This document explains the internal architecture of Trotsky, its design principles, and how the different components work together.
+
+## Overview
+
+Trotsky is built around a **builder pattern** that allows users to chain operations (called "steps") to interact with the AT Protocol / Bluesky API. The library provides a type-safe, fluent interface for building complex automation workflows.
+
+## Core Concepts
+
+### 1. Steps
+
+A **Step** is the fundamental building block in Trotsky. Each step represents a single operation, such as:
+- Fetching an actor profile
+- Liking a post
+- Following an account
+- Iterating through a list
+
+Steps are chainable and composable, allowing complex workflows to be built declaratively.
+
+```typescript
+await Trotsky.init(agent)
+ .actor('alice.bsky.social') // Step 1: Get actor
+ .followers() // Step 2: Get followers
+ .each() // Step 3: Iterate
+ .follow() // Step 4: Follow each
+ .run() // Execute
+```
+
+### 2. Step Hierarchy
+
+Steps are organized in a class hierarchy:
+
+```
+Step (base class)
+├── StepBuilder (chainable steps)
+│ ├── Trotsky (entry point)
+│ ├── StepActor
+│ ├── StepPost
+│ └── ...
+├── StepBuilderList (steps that return lists)
+│ ├── StepActors
+│ ├── StepPosts
+│ ├── StepActorFollowers
+│ └── ...
+└── StepBuilderStream (steps that stream data)
+ ├── StepStreamPosts
+ └── StepActorStreamPosts
+```
+
+**Key Properties:**
+- **Parent**: Each step has a reference to its parent step
+- **Context**: Data passed from parent to child (e.g., actor DID)
+- **Output**: Result of executing the step
+- **Agent**: AT Protocol agent for API calls
+
+### 3. Step Types
+
+#### Single-Item Steps
+Steps that work with a single entity:
+- `StepActor` - Single actor profile
+- `StepPost` - Single post
+- `StepList` - Single list
+
+#### List Steps
+Steps that work with collections and support pagination:
+- `StepActors` - Multiple actors
+- `StepPosts` - Multiple posts
+- `StepActorFollowers` - Actor's followers (paginated)
+
+#### Action Steps
+Steps that perform an action without returning data:
+- `StepActorFollow` - Follow an actor
+- `StepPostLike` - Like a post
+- `StepActorBlock` - Block an actor
+
+#### Utility Steps
+Steps that modify execution flow:
+- `StepWhen` - Conditional execution
+- `StepTap` - Side effects without modifying flow
+- `StepWait` - Delay execution
+- `StepSave` - Save output to file
+
+## Component Organization
+
+### Directory Structure
+
+```
+lib/
+├── core/ # Core step implementations
+│ ├── base/ # Base classes (Step, StepBuilder, etc.)
+│ ├── mixins/ # Reusable mixins (ActorMixins, PostMixins)
+│ └── utils/ # Utilities (logger, resolvable, etc.)
+├── types/ # Shared type definitions
+├── errors/ # Custom error classes
+├── config/ # Configuration types
+└── trotsky.ts # Main barrel export
+```
+
+### Key Files
+
+- **`Step.ts`**: Base class for all steps
+- **`StepBuilder.ts`**: Base for chainable steps
+- **`StepBuilderList.ts`**: Base for list/collection steps
+- **`Trotsky.ts`**: Main entry point class
+- **`trotsky.ts`**: Barrel export file
+
+## Design Patterns
+
+### 1. Builder Pattern
+
+The fluent interface allows chaining operations:
+
+```typescript
+Trotsky.init(agent)
+ .actor('handle')
+ .posts()
+ .each()
+ .like()
+```
+
+Each method returns a new step instance that can be chained further.
+
+### 2. Mixins
+
+Common functionality is shared via mixins:
+
+```typescript
+// ActorMixins.ts
+export class ActorMixins {
+ followers() { return this.append(StepActorFollowers) }
+ posts() { return this.append(StepActorPosts) }
+ starterPacks() { return this.append(StepActorStarterPacks) }
+}
+
+// StepActor extends both StepBuilder and ActorMixins
+export class StepActor extends mix(StepBuilder, ActorMixins) {}
+```
+
+**Benefits:**
+- Code reuse across similar steps
+- Consistent API across step types
+- Easy to add new functionality
+
+### 3. Context Propagation
+
+Data flows from parent to child through the context property:
+
+```typescript
+Trotsky.init(agent)
+ .actor('alice') // Context: { did: 'did:plc:...', handle: 'alice', ... }
+ .followers() // Context inherited from parent
+ .each() // Context: individual follower
+ .follow() // Uses follower's DID from context
+```
+
+### 4. Lazy Execution
+
+Steps are not executed when chained - only when `.run()` is called:
+
+```typescript
+const workflow = Trotsky.init(agent)
+ .actor('alice')
+ .posts()
+ .each()
+ .like()
+// Nothing has executed yet
+
+await workflow.run() // NOW it executes
+```
+
+## Data Flow
+
+### 1. Execution Pipeline
+
+```
+User Code → Trotsky.init() → Chain Steps → .run() → Execute Pipeline
+ ↓
+ Results/Side Effects
+```
+
+### 2. Step Execution
+
+Each step follows this lifecycle:
+
+1. **Construction**: Step is created via `.append()`
+2. **Configuration**: Parameters are set (e.g., query params)
+3. **Context Inheritance**: Receives context from parent
+4. **Execution**: `.apply()` method is called
+5. **Output Generation**: Result is stored in `.output`
+6. **Child Execution**: Child steps receive this step's output as context
+
+### 3. Pagination
+
+List steps handle pagination automatically:
+
+```typescript
+async applyPagination() {
+ let cursor: string | undefined
+ const items = []
+
+ while (true) {
+ const response = await this.agent.api({ cursor, limit: 50 })
+ items.push(...response.items)
+
+ cursor = response.cursor
+ if (!cursor || items.length >= limit) break
+ }
+
+ this.output = items
+}
+```
+
+## Error Handling
+
+Trotsky provides structured error classes:
+
+```typescript
+try {
+ await Trotsky.init(agent).actor('invalid').run()
+} catch (error) {
+ if (error instanceof ValidationError) {
+ console.log(error.code, error.details)
+ } else if (error instanceof AuthenticationError) {
+ console.log('Auth failed:', error.message)
+ }
+}
+```
+
+**Error Classes:**
+- `TrotskyError` - Base error class
+- `ValidationError` - Input validation failures
+- `AuthenticationError` - Auth/permission failures
+- `RateLimitError` - Rate limit exceeded
+- `PaginationError` - Pagination failures
+
+## Type Safety
+
+Trotsky leverages TypeScript's type system extensively:
+
+### 1. Generic Type Parameters
+
+```typescript
+class Step
{
+ parent: P // Parent step type
+ context: C // Context data type
+ output: O // Output data type
+}
+```
+
+### 2. Type Inference
+
+Types are inferred through the chain:
+
+```typescript
+const result = await Trotsky.init(agent)
+ .actor('alice') // StepActor
+ .posts() // StepActorPosts>
+ .runHere()
+
+// result.output is typed as AppBskyFeedDefs.PostView[]
+```
+
+### 3. Shared Types
+
+Common types are centralized in `lib/types/`:
+
+```typescript
+import { ActorParam, PostUri, PaginationParams } from 'trotsky/types'
+```
+
+## Performance Considerations
+
+### 1. Pagination Limits
+
+Control pagination with `.take()`:
+
+```typescript
+// Only fetch first 10 items
+await Trotsky.init(agent)
+ .actor('alice')
+ .followers()
+ .take(10)
+ .run()
+```
+
+### 2. Rate Limiting
+
+Built-in rate limiting (configurable):
+
+```typescript
+Trotsky.init(agent, {
+ rateLimit: {
+ enabled: true,
+ requestsPerMinute: 60
+ }
+})
+```
+
+### 3. Batching
+
+Some operations support batching:
+
+```typescript
+// Fetch multiple posts in one request
+await Trotsky.init(agent).posts([uri1, uri2, uri3]).run()
+```
+
+## Extensibility
+
+### Adding New Steps
+
+1. **Create Step Class**:
+```typescript
+export class StepMyFeature extends StepBuilder {
+ async apply() {
+ const result = await this.agent.api.myFeature()
+ this.output = result
+ }
+}
+```
+
+2. **Add to Trotsky Class**:
+```typescript
+myFeature(): StepMyFeature {
+ return this.append(StepMyFeature)
+}
+```
+
+3. **Export**:
+```typescript
+// lib/trotsky.ts
+export * from "./core/StepMyFeature"
+```
+
+### Using Mixins
+
+Add reusable functionality via mixins:
+
+```typescript
+export class MyMixins {
+ customAction() {
+ return this.append(StepCustomAction)
+ }
+}
+
+export class StepMyFeature extends mix(StepBuilder, MyMixins) {}
+```
+
+## Testing Strategy
+
+- **Unit Tests**: Test individual steps in isolation
+- **Integration Tests**: Test step chains and workflows
+- **Test Environment**: Uses `@atproto/dev-env` for realistic testing
+
+```typescript
+describe('StepActor', () => {
+ test('should fetch actor profile', async () => {
+ const actor = await Trotsky.init(agent)
+ .actor('alice')
+ .runHere()
+
+ expect(actor.output).toHaveProperty('handle')
+ })
+})
+```
+
+## Future Architecture Plans
+
+### 1. Plugin System
+
+Support for custom plugins:
+
+```typescript
+Trotsky.init(agent)
+ .use(new AnalyticsPlugin())
+ .use(new CachePlugin())
+```
+
+### 2. Middleware
+
+Request/response interceptors:
+
+```typescript
+Trotsky.init(agent)
+ .beforeStep((step) => console.log(`Executing: ${step.name}`))
+ .afterStep((step) => console.log(`Completed: ${step.name}`))
+```
+
+### 3. Advanced Caching
+
+Built-in caching layer for frequently accessed data:
+
+```typescript
+Trotsky.init(agent, {
+ cache: {
+ enabled: true,
+ ttl: 60000
+ }
+})
+```
+
+## Best Practices
+
+1. **Use Type Inference**: Let TypeScript infer types instead of explicit annotations
+2. **Chain Efficiently**: Minimize API calls by batching when possible
+3. **Handle Errors**: Always wrap `.run()` in try/catch
+4. **Rate Limit**: Use `.wait()` between actions to avoid rate limits
+5. **Test Workflows**: Write integration tests for complex chains
+
+## Contributing
+
+When contributing to Trotsky's architecture:
+
+1. Follow existing patterns (Step hierarchy, mixins, etc.)
+2. Add comprehensive JSDoc comments
+3. Include unit and integration tests
+4. Update this architecture document
+5. Consider backward compatibility
+
+## References
+
+- [AT Protocol Documentation](https://atproto.com)
+- [Bluesky API Reference](https://docs.bsky.app)
+- [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
diff --git a/lib/config/TrotskyConfig.ts b/lib/config/TrotskyConfig.ts
new file mode 100644
index 0000000..f487edd
--- /dev/null
+++ b/lib/config/TrotskyConfig.ts
@@ -0,0 +1,208 @@
+/**
+ * Configuration options for Trotsky instance.
+ *
+ * This module provides configuration interfaces and default values
+ * for customizing Trotsky's behavior.
+ *
+ * @module config
+ */
+
+/**
+ * Logging configuration options.
+ *
+ * @public
+ */
+export interface LoggingConfig {
+
+ /** Enable or disable logging */
+ "enabled": boolean;
+
+ /** Minimum log level to output */
+ "level": "debug" | "info" | "warn" | "error";
+
+ /** Custom logger function (optional) */
+ "logger"?: (level: string, message: string, meta?: Record) => void;
+}
+
+/**
+ * Pagination configuration options.
+ *
+ * @public
+ */
+export interface PaginationConfig {
+
+ /** Default page size for paginated requests */
+ "defaultLimit": number;
+
+ /** Maximum page size allowed */
+ "maxLimit": number;
+
+ /** Enable automatic pagination */
+ "autoPaginate": boolean;
+}
+
+/**
+ * Retry configuration options.
+ *
+ * @public
+ */
+export interface RetryConfig {
+
+ /** Enable automatic retries on failure */
+ "enabled": boolean;
+
+ /** Maximum number of retry attempts */
+ "maxAttempts": number;
+
+ /** Backoff strategy for retries */
+ "backoff": "linear" | "exponential";
+
+ /** Initial delay between retries (milliseconds) */
+ "initialDelay": number;
+
+ /** Maximum delay between retries (milliseconds) */
+ "maxDelay": number;
+
+ /** HTTP status codes that should trigger a retry */
+ "retryableStatusCodes": number[];
+}
+
+/**
+ * Rate limiting configuration options.
+ *
+ * @public
+ */
+export interface RateLimitConfig {
+
+ /** Enable built-in rate limiting */
+ "enabled": boolean;
+
+ /** Maximum requests per minute */
+ "requestsPerMinute": number;
+
+ /** Maximum concurrent requests */
+ "concurrentRequests": number;
+
+ /** Behavior when rate limit is hit */
+ "onLimitReached": "throw" | "queue" | "drop";
+}
+
+/**
+ * Caching configuration options.
+ *
+ * @public
+ */
+export interface CacheConfig {
+
+ /** Enable caching */
+ "enabled": boolean;
+
+ /** Default cache TTL in milliseconds */
+ "defaultTTL": number;
+
+ /** Maximum cache size (number of entries) */
+ "maxSize": number;
+
+ /** Cache key prefix */
+ "keyPrefix": string;
+}
+
+/**
+ * Complete Trotsky configuration.
+ *
+ * @public
+ */
+export interface TrotskyConfig {
+
+ /** Logging configuration */
+ "logging": LoggingConfig;
+
+ /** Pagination configuration */
+ "pagination": PaginationConfig;
+
+ /** Retry configuration */
+ "retry": RetryConfig;
+
+ /** Rate limiting configuration */
+ "rateLimit": RateLimitConfig;
+
+ /** Caching configuration */
+ "cache": CacheConfig;
+}
+
+/**
+ * Partial configuration allowing users to override specific options.
+ *
+ * @public
+ */
+export type PartialTrotskyConfig = {
+ [K in keyof TrotskyConfig]?: Partial
+}
+
+/**
+ * Default configuration values.
+ *
+ * These values are used when no custom configuration is provided.
+ *
+ * @public
+ */
+export const defaultConfig: TrotskyConfig = {
+ "logging": {
+ "enabled": false,
+ "level": "info"
+ },
+ "pagination": {
+ "defaultLimit": 50,
+ "maxLimit": 100,
+ "autoPaginate": true
+ },
+ "retry": {
+ "enabled": true,
+ "maxAttempts": 3,
+ "backoff": "exponential",
+ "initialDelay": 1000,
+ "maxDelay": 30000,
+ "retryableStatusCodes": [408, 429, 500, 502, 503, 504]
+ },
+ "rateLimit": {
+ "enabled": false,
+ "requestsPerMinute": 60,
+ "concurrentRequests": 10,
+ "onLimitReached": "queue"
+ },
+ "cache": {
+ "enabled": false,
+ "defaultTTL": 60000, // 1 minute
+ "maxSize": 1000,
+ "keyPrefix": "trotsky:"
+ }
+}
+
+/**
+ * Merges partial configuration with default configuration.
+ *
+ * @param config - Partial configuration to merge
+ * @returns Complete configuration with defaults
+ *
+ * @example
+ * ```ts
+ * const config = mergeConfig({
+ * logging: { enabled: true, level: "debug" }
+ * })
+ * ```
+ *
+ * @public
+ */
+export function mergeConfig (config?: PartialTrotskyConfig): TrotskyConfig {
+ if (!config) {
+ return { ...defaultConfig }
+ }
+
+ return {
+ "logging": { ...defaultConfig.logging, ...config.logging },
+ "pagination": { ...defaultConfig.pagination, ...config.pagination },
+ "retry": { ...defaultConfig.retry, ...config.retry },
+ "rateLimit": { ...defaultConfig.rateLimit, ...config.rateLimit },
+ "cache": { ...defaultConfig.cache, ...config.cache }
+ }
+}
diff --git a/lib/config/index.ts b/lib/config/index.ts
new file mode 100644
index 0000000..7d9f047
--- /dev/null
+++ b/lib/config/index.ts
@@ -0,0 +1,21 @@
+/**
+ * Central export point for Trotsky configuration.
+ *
+ * @module config
+ * @packageDocumentation
+ */
+
+export type {
+ LoggingConfig,
+ PaginationConfig,
+ RetryConfig,
+ RateLimitConfig,
+ CacheConfig,
+ TrotskyConfig,
+ PartialTrotskyConfig
+} from "./TrotskyConfig"
+
+export {
+ defaultConfig,
+ mergeConfig
+} from "./TrotskyConfig"
diff --git a/lib/errors/AuthenticationError.ts b/lib/errors/AuthenticationError.ts
new file mode 100644
index 0000000..928559f
--- /dev/null
+++ b/lib/errors/AuthenticationError.ts
@@ -0,0 +1,63 @@
+/**
+ * Error class for authentication and authorization failures.
+ *
+ * Thrown when authentication is required but missing, invalid,
+ * or when the user lacks permission for an operation.
+ *
+ * @example
+ * ```ts
+ * throw new AuthenticationError(
+ * "Authentication required for this operation",
+ * "AUTH_REQUIRED",
+ * "StepActorFollow"
+ * )
+ * ```
+ *
+ * @public
+ */
+
+import { TrotskyError } from "./TrotskyError"
+
+export class AuthenticationError extends TrotskyError {
+
+ /**
+ * Creates a new AuthenticationError.
+ *
+ * @param message - Human-readable error message
+ * @param code - Machine-readable error code
+ * @param step - Optional step name where error occurred
+ * @param cause - Optional underlying error
+ */
+ constructor (
+ message: string,
+ code: string = "AUTH_ERROR",
+ step?: string,
+ cause?: Error
+ ) {
+ super(message, code, step, cause)
+ this.name = "AuthenticationError"
+ }
+}
+
+/**
+ * Common authentication error codes.
+ *
+ * @public
+ */
+export const AuthenticationErrorCode = {
+
+ /** Authentication is required but not provided */
+ "AUTH_REQUIRED": "AUTH_REQUIRED",
+
+ /** Provided credentials are invalid */
+ "INVALID_CREDENTIALS": "INVALID_CREDENTIALS",
+
+ /** Session has expired */
+ "SESSION_EXPIRED": "SESSION_EXPIRED",
+
+ /** User lacks permission for this operation */
+ "FORBIDDEN": "FORBIDDEN",
+
+ /** Agent is not authenticated */
+ "NOT_AUTHENTICATED": "NOT_AUTHENTICATED"
+} as const
diff --git a/lib/errors/PaginationError.ts b/lib/errors/PaginationError.ts
new file mode 100644
index 0000000..bb83a45
--- /dev/null
+++ b/lib/errors/PaginationError.ts
@@ -0,0 +1,63 @@
+/**
+ * Error class for pagination-related failures.
+ *
+ * Thrown when pagination operations fail, such as invalid cursors,
+ * cursor expiration, or pagination API errors.
+ *
+ * @example
+ * ```ts
+ * throw new PaginationError(
+ * "Invalid pagination cursor",
+ * "INVALID_CURSOR",
+ * "StepActorFollowers"
+ * )
+ * ```
+ *
+ * @public
+ */
+
+import { TrotskyError } from "./TrotskyError"
+
+export class PaginationError extends TrotskyError {
+
+ /**
+ * Creates a new PaginationError.
+ *
+ * @param message - Human-readable error message
+ * @param code - Machine-readable error code
+ * @param step - Optional step name where error occurred
+ * @param cause - Optional underlying error
+ */
+ constructor (
+ message: string,
+ code: string = "PAGINATION_ERROR",
+ step?: string,
+ cause?: Error
+ ) {
+ super(message, code, step, cause)
+ this.name = "PaginationError"
+ }
+}
+
+/**
+ * Common pagination error codes.
+ *
+ * @public
+ */
+export const PaginationErrorCode = {
+
+ /** Cursor is invalid or malformed */
+ "INVALID_CURSOR": "INVALID_CURSOR",
+
+ /** Cursor has expired */
+ "CURSOR_EXPIRED": "CURSOR_EXPIRED",
+
+ /** Failed to fetch next page */
+ "FETCH_FAILED": "FETCH_FAILED",
+
+ /** Limit parameter is invalid */
+ "INVALID_LIMIT": "INVALID_LIMIT",
+
+ /** No more pages available */
+ "NO_MORE_PAGES": "NO_MORE_PAGES"
+} as const
diff --git a/lib/errors/RateLimitError.ts b/lib/errors/RateLimitError.ts
new file mode 100644
index 0000000..e42f597
--- /dev/null
+++ b/lib/errors/RateLimitError.ts
@@ -0,0 +1,81 @@
+/**
+ * Error class for rate limiting failures.
+ *
+ * Thrown when API rate limits are exceeded or when rate limiting
+ * is enforced by Trotsky's internal rate limiter.
+ *
+ * @example
+ * ```ts
+ * throw new RateLimitError(
+ * "Rate limit exceeded. Retry after 60 seconds",
+ * "RATE_LIMIT_EXCEEDED",
+ * "StepSearchPosts",
+ * 60
+ * )
+ * ```
+ *
+ * @public
+ */
+
+import { TrotskyError } from "./TrotskyError"
+
+export class RateLimitError extends TrotskyError {
+
+ /**
+ * Number of seconds until rate limit resets (if known).
+ */
+ public readonly retryAfter?: number
+
+ /**
+ * Creates a new RateLimitError.
+ *
+ * @param message - Human-readable error message
+ * @param code - Machine-readable error code
+ * @param step - Optional step name where error occurred
+ * @param retryAfter - Optional seconds until retry is allowed
+ * @param cause - Optional underlying error
+ */
+ constructor (
+ message: string,
+ code: string = "RATE_LIMIT_ERROR",
+ step?: string,
+ retryAfter?: number,
+ cause?: Error
+ ) {
+ super(message, code, step, cause)
+ this.name = "RateLimitError"
+ this.retryAfter = retryAfter
+ }
+
+ /**
+ * Returns a JSON representation including retry information.
+ *
+ * @returns Object containing error details with retry info
+ */
+ override toJSON () {
+ return {
+ ...super.toJSON(),
+ "retryAfter": this.retryAfter
+ }
+ }
+}
+
+/**
+ * Common rate limit error codes.
+ *
+ * @public
+ */
+export const RateLimitErrorCode = {
+
+ /** API rate limit exceeded */
+ "RATE_LIMIT_EXCEEDED": "RATE_LIMIT_EXCEEDED",
+
+ /** Too many requests */
+ "TOO_MANY_REQUESTS": "TOO_MANY_REQUESTS",
+
+ /** Daily quota exceeded */
+ "QUOTA_EXCEEDED": "QUOTA_EXCEEDED",
+
+ /** Concurrent request limit exceeded */
+ "CONCURRENT_LIMIT": "CONCURRENT_LIMIT"
+} as const
diff --git a/lib/errors/TrotskyError.ts b/lib/errors/TrotskyError.ts
new file mode 100644
index 0000000..41486b7
--- /dev/null
+++ b/lib/errors/TrotskyError.ts
@@ -0,0 +1,107 @@
+/**
+ * Base error class for all Trotsky-related errors.
+ *
+ * This class extends the standard Error class and provides additional
+ * context such as error codes, step information, and causal errors.
+ *
+ * @example
+ * ```ts
+ * throw new TrotskyError(
+ * "Failed to fetch actor",
+ * "ACTOR_NOT_FOUND",
+ * "StepActor"
+ * )
+ * ```
+ *
+ * @public
+ */
+export class TrotskyError extends Error {
+
+ /**
+ * Error code for programmatic handling.
+ */
+ public readonly code: string
+
+ /**
+ * Name of the step where the error occurred (if applicable).
+ */
+ public readonly step?: string
+
+ /**
+ * Original error that caused this error (if applicable).
+ */
+ public override readonly cause?: Error
+
+ /**
+ * Timestamp when the error was created.
+ */
+ public readonly timestamp: Date
+
+ /**
+ * Creates a new TrotskyError.
+ *
+ * @param message - Human-readable error message
+ * @param code - Machine-readable error code
+ * @param step - Optional step name where error occurred
+ * @param cause - Optional underlying error that caused this error
+ */
+ constructor (
+ message: string,
+ code: string,
+ step?: string,
+ cause?: Error
+ ) {
+ super(message)
+ this.name = "TrotskyError"
+ this.code = code
+ this.step = step
+ this.cause = cause
+ this.timestamp = new Date()
+
+ // Maintains proper stack trace for where error was thrown (V8 only)
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, TrotskyError)
+ }
+ }
+
+ /**
+ * Returns a JSON representation of the error.
+ *
+ * @returns Object containing error details
+ */
+ toJSON () {
+ return {
+ "name": this.name,
+ "message": this.message,
+ "code": this.code,
+ "step": this.step,
+ "timestamp": this.timestamp.toISOString(),
+ "stack": this.stack,
+ "cause": this.cause ? {
+ "name": this.cause.name,
+ "message": this.cause.message
+ } : undefined
+ }
+ }
+
+ /**
+ * Returns a formatted string representation of the error.
+ *
+ * @returns Formatted error string
+ */
+ override toString (): string {
+ const parts = [
+ `${this.name} [${this.code}]: ${this.message}`
+ ]
+
+ if (this.step) {
+ parts.push(`at step: ${this.step}`)
+ }
+
+ if (this.cause) {
+ parts.push(`caused by: ${this.cause.message}`)
+ }
+
+ return parts.join(" ")
+ }
+}
diff --git a/lib/errors/ValidationError.ts b/lib/errors/ValidationError.ts
new file mode 100644
index 0000000..244cefb
--- /dev/null
+++ b/lib/errors/ValidationError.ts
@@ -0,0 +1,87 @@
+/**
+ * Error class for validation failures.
+ *
+ * Thrown when input validation fails, such as invalid URIs,
+ * malformed parameters, or constraint violations.
+ *
+ * @example
+ * ```ts
+ * throw new ValidationError(
+ * "Invalid AT URI format",
+ * "INVALID_URI",
+ * "StepPost",
+ * { uri: "invalid://uri" }
+ * )
+ * ```
+ *
+ * @public
+ */
+
+import { TrotskyError } from "./TrotskyError"
+
+export class ValidationError extends TrotskyError {
+
+ /**
+ * Additional validation details (field names, values, etc.).
+ */
+ public readonly details?: Record
+
+ /**
+ * Creates a new ValidationError.
+ *
+ * @param message - Human-readable error message
+ * @param code - Machine-readable error code
+ * @param step - Optional step name where error occurred
+ * @param details - Optional validation details
+ * @param cause - Optional underlying error
+ */
+ constructor (
+ message: string,
+ code: string = "VALIDATION_ERROR",
+ step?: string,
+ details?: Record,
+ cause?: Error
+ ) {
+ super(message, code, step, cause)
+ this.name = "ValidationError"
+ this.details = details
+ }
+
+ /**
+ * Returns a JSON representation including validation details.
+ *
+ * @returns Object containing error details with validation info
+ */
+ override toJSON () {
+ return {
+ ...super.toJSON(),
+ "details": this.details
+ }
+ }
+}
+
+/**
+ * Common validation error codes.
+ *
+ * @public
+ */
+export const ValidationErrorCode = {
+
+ /** URI is invalid or malformed */
+ "INVALID_URI": "INVALID_URI",
+
+ /** Parameter is missing */
+ "MISSING_PARAM": "MISSING_PARAM",
+
+ /** Parameter value is invalid */
+ "INVALID_PARAM": "INVALID_PARAM",
+
+ /** Parameter type is incorrect */
+ "INVALID_TYPE": "INVALID_TYPE",
+
+ /** Parameter value is out of range */
+ "OUT_OF_RANGE": "OUT_OF_RANGE",
+
+ /** Required field is missing */
+ "REQUIRED_FIELD": "REQUIRED_FIELD"
+} as const
diff --git a/lib/errors/index.ts b/lib/errors/index.ts
new file mode 100644
index 0000000..e648ccb
--- /dev/null
+++ b/lib/errors/index.ts
@@ -0,0 +1,132 @@
+/**
+ * Central export point for all Trotsky error classes.
+ *
+ * This module provides custom error classes for different failure scenarios,
+ * making it easier to handle and diagnose errors in Trotsky operations.
+ *
+ * @example
+ * ```ts
+ * import { PaginationError, ValidationError } from "trotsky/errors"
+ *
+ * try {
+ * await trotsky.actor("handle").followers().run()
+ * } catch (error) {
+ * if (error instanceof PaginationError) {
+ * console.error("Pagination failed:", error.code)
+ * } else if (error instanceof ValidationError) {
+ * console.error("Validation failed:", error.details)
+ * }
+ * }
+ * ```
+ *
+ * @module errors
+ * @packageDocumentation
+ */
+
+// Base error class
+export { TrotskyError } from "./TrotskyError"
+
+// Specific error classes
+export {
+ PaginationError,
+ PaginationErrorCode
+} from "./PaginationError"
+
+export {
+ AuthenticationError,
+ AuthenticationErrorCode
+} from "./AuthenticationError"
+
+export {
+ RateLimitError,
+ RateLimitErrorCode
+} from "./RateLimitError"
+
+export {
+ ValidationError,
+ ValidationErrorCode
+} from "./ValidationError"
+
+// Import for use in fromXRPCError function
+import { TrotskyError as TrotskyErrorClass } from "./TrotskyError"
+import { AuthenticationError as AuthenticationErrorClass } from "./AuthenticationError"
+import { RateLimitError as RateLimitErrorClass } from "./RateLimitError"
+
+/**
+ * Type guard to check if an error is a Trotsky error.
+ *
+ * @param error - Error to check
+ * @returns True if error is a TrotskyError instance
+ *
+ * @example
+ * ```ts
+ * if (isTrotskyError(error)) {
+ * console.log(error.code, error.step)
+ * }
+ * ```
+ *
+ * @public
+ */
+export function isTrotskyError (error: unknown): error is import("./TrotskyError").TrotskyError {
+ return error instanceof Error && "code" in error && "step" in error
+}
+
+/**
+ * Helper to create error from AT Protocol XRPCError.
+ *
+ * @param error - XRPC error from AT Protocol client
+ * @param step - Step name where error occurred
+ * @returns Appropriate Trotsky error instance
+ *
+ * @example
+ * ```ts
+ * try {
+ * await agent.getProfile({ actor: did })
+ * } catch (err) {
+ * throw fromXRPCError(err, "StepActor")
+ * }
+ * ```
+ *
+ * @public
+ */
+export function fromXRPCError (error: unknown, step?: string): import("./TrotskyError").TrotskyError {
+ // Type guard to check if error has expected properties
+ const err = error as {
+ "status"?: number;
+ "message"?: string;
+ "error"?: string;
+ "headers"?: Record;
+ }
+
+ // Check for common XRPC error statuses
+ if (err.status === 401 || err.status === 403) {
+ return new AuthenticationErrorClass(
+ err.message || "Authentication failed",
+ err.status === 401 ? "NOT_AUTHENTICATED" : "FORBIDDEN",
+ step,
+ error instanceof Error ? error : undefined
+ )
+ }
+
+ if (err.status === 429) {
+ const retryAfter = err.headers?.["retry-after"]
+ ? parseInt(err.headers["retry-after"], 10)
+ : undefined
+
+ return new RateLimitErrorClass(
+ err.message || "Rate limit exceeded",
+ "RATE_LIMIT_EXCEEDED",
+ step,
+ retryAfter,
+ error instanceof Error ? error : undefined
+ )
+ }
+
+ // Default to generic TrotskyError
+ return new TrotskyErrorClass(
+ err.message || "Unknown error",
+ err.error || "UNKNOWN_ERROR",
+ step,
+ error instanceof Error ? error : undefined
+ )
+}
diff --git a/lib/types/actor.ts b/lib/types/actor.ts
new file mode 100644
index 0000000..2d9a2f3
--- /dev/null
+++ b/lib/types/actor.ts
@@ -0,0 +1,56 @@
+/**
+ * Type definitions for actor-related operations.
+ * @module types/actor
+ */
+
+import type { AppBskyActorDefs, AtUri } from "@atproto/api"
+
+/**
+ * Parameter type for identifying a single actor.
+ * Can be either a DID string, handle string, or AtUri object.
+ *
+ * @example
+ * ```ts
+ * const actor1: ActorParam = "did:plc:example"
+ * const actor2: ActorParam = "alice.bsky.social"
+ * const actor3: ActorParam = new AtUri("at://did:plc:example/...")
+ * ```
+ *
+ * @public
+ */
+export type ActorParam = string | AtUri
+
+/**
+ * Parameter type for identifying multiple actors.
+ *
+ * @public
+ */
+export type ActorsParam = ActorParam[]
+
+/**
+ * Output type for a single actor profile.
+ *
+ * @public
+ */
+export type ActorOutput = AppBskyActorDefs.ProfileViewDetailed
+
+/**
+ * Output type for multiple actor profiles.
+ *
+ * @public
+ */
+export type ActorsOutput = AppBskyActorDefs.ProfileView[]
+
+/**
+ * Output type for basic actor profile information.
+ *
+ * @public
+ */
+export type ActorProfileView = AppBskyActorDefs.ProfileView
+
+/**
+ * Output type for detailed actor profile information.
+ *
+ * @public
+ */
+export type ActorProfileViewDetailed = AppBskyActorDefs.ProfileViewDetailed
diff --git a/lib/types/index.ts b/lib/types/index.ts
new file mode 100644
index 0000000..a8680ce
--- /dev/null
+++ b/lib/types/index.ts
@@ -0,0 +1,47 @@
+/**
+ * Central export point for all shared type definitions.
+ *
+ * This module provides type definitions used throughout Trotsky,
+ * offering a single source of truth for common types across the library.
+ *
+ * @module types
+ * @packageDocumentation
+ */
+
+// Actor types
+export type {
+ ActorParam,
+ ActorsParam,
+ ActorOutput,
+ ActorsOutput,
+ ActorProfileView,
+ ActorProfileViewDetailed
+} from "./actor"
+
+// Post types
+export type {
+ PostUri,
+ PostsUris,
+ PostOutput,
+ PostsOutput,
+ PostRecord,
+ CreatePostParams,
+ ReplyParams
+} from "./post"
+
+// List types
+export type {
+ ListUri,
+ ListsUris,
+ ListOutput,
+ ListsOutput,
+ ListItemView,
+ ListPurpose
+} from "./list"
+
+// Pagination types
+export type {
+ PaginationParams,
+ PaginatedResponse,
+ PaginatedQueryParams
+} from "./pagination"
diff --git a/lib/types/list.ts b/lib/types/list.ts
new file mode 100644
index 0000000..171d788
--- /dev/null
+++ b/lib/types/list.ts
@@ -0,0 +1,57 @@
+/**
+ * Type definitions for list-related operations.
+ * @module types/list
+ */
+
+import type { AppBskyGraphDefs, AtUri } from "@atproto/api"
+
+/**
+ * Parameter type for identifying a single list by its URI.
+ *
+ * @example
+ * ```ts
+ * const list: ListUri = "at://did:plc:example/app.bsky.graph.list/listid"
+ * ```
+ *
+ * @public
+ */
+export type ListUri = string | AtUri
+
+/**
+ * Parameter type for identifying multiple lists by their URIs.
+ *
+ * @public
+ */
+export type ListsUris = ListUri[]
+
+/**
+ * Output type for a single list view.
+ *
+ * @public
+ */
+export type ListOutput = AppBskyGraphDefs.ListView
+
+/**
+ * Output type for multiple list views.
+ *
+ * @public
+ */
+export type ListsOutput = AppBskyGraphDefs.ListView[]
+
+/**
+ * Output type for list item views (members of a list).
+ *
+ * @public
+ */
+export type ListItemView = AppBskyGraphDefs.ListItemView
+
+/**
+ * Purpose/type of a list.
+ *
+ * @public
+ */
+export type ListPurpose =
+ | "app.bsky.graph.defs#modlist"
+ | "app.bsky.graph.defs#curatelist"
+ | "app.bsky.graph.defs#referencelist"
+ | (string & {})
diff --git a/lib/types/pagination.ts b/lib/types/pagination.ts
new file mode 100644
index 0000000..d9cb953
--- /dev/null
+++ b/lib/types/pagination.ts
@@ -0,0 +1,41 @@
+/**
+ * Type definitions for pagination-related operations.
+ * @module types/pagination
+ */
+
+/**
+ * Standard pagination parameters used across AT Protocol APIs.
+ *
+ * @public
+ */
+export interface PaginationParams {
+
+ /** Maximum number of items to return per page */
+ "limit"?: number;
+
+ /** Cursor for pagination (opaque string from previous response) */
+ "cursor"?: string;
+}
+
+/**
+ * Standard paginated response structure.
+ *
+ * @typeParam T - The type of items in the paginated response
+ * @public
+ */
+export interface PaginatedResponse {
+
+ /** Array of items for this page */
+ "items": T[];
+
+ /** Cursor for fetching the next page (undefined if no more pages) */
+ "cursor"?: string;
+}
+
+/**
+ * Query parameters with pagination support.
+ *
+ * @typeParam T - Additional query parameters specific to the endpoint
+ * @public
+ */
+export type PaginatedQueryParams> = T & PaginationParams
diff --git a/lib/types/post.ts b/lib/types/post.ts
new file mode 100644
index 0000000..c6229cb
--- /dev/null
+++ b/lib/types/post.ts
@@ -0,0 +1,99 @@
+/**
+ * Type definitions for post-related operations.
+ * @module types/post
+ */
+
+import type { AppBskyFeedDefs, AppBskyFeedPost, AtUri } from "@atproto/api"
+
+/**
+ * Parameter type for identifying a single post by its URI.
+ *
+ * @example
+ * ```ts
+ * const post1: PostUri = "at://did:plc:example/app.bsky.feed.post/postid"
+ * const post2: PostUri = new AtUri("at://...")
+ * ```
+ *
+ * @public
+ */
+export type PostUri = string | AtUri
+
+/**
+ * Parameter type for identifying multiple posts by their URIs.
+ *
+ * @public
+ */
+export type PostsUris = PostUri[]
+
+/**
+ * Output type for a single post view.
+ *
+ * @public
+ */
+export type PostOutput = AppBskyFeedDefs.PostView
+
+/**
+ * Output type for multiple post views.
+ *
+ * @public
+ */
+export type PostsOutput = AppBskyFeedDefs.PostView[]
+
+/**
+ * Type for post record data.
+ *
+ * @public
+ */
+export type PostRecord = AppBskyFeedPost.Record
+
+/**
+ * Parameters for creating a new post.
+ *
+ * @public
+ */
+export interface CreatePostParams {
+
+ /** The text content of the post */
+ "text": string;
+
+ /** Optional facets for rich text (links, mentions, etc.) */
+ "facets"?: AppBskyFeedPost.Record["facets"];
+
+ /** Optional reply reference */
+ "reply"?: AppBskyFeedPost.Record["reply"];
+
+ /** Optional embed (images, external links, etc.) */
+ "embed"?: AppBskyFeedPost.Record["embed"];
+
+ /** Optional language tags */
+ "langs"?: string[];
+
+ /** Optional labels */
+ "labels"?: AppBskyFeedPost.Record["labels"];
+
+ /** Optional tags */
+ "tags"?: string[];
+
+ /** Creation timestamp (defaults to now) */
+ "createdAt"?: string;
+}
+
+/**
+ * Parameters for replying to a post.
+ *
+ * @public
+ */
+export interface ReplyParams {
+
+ /** The text content of the reply */
+ "text": string;
+
+ /** Optional facets for rich text */
+ "facets"?: AppBskyFeedPost.Record["facets"];
+
+ /** Optional embed */
+ "embed"?: AppBskyFeedPost.Record["embed"];
+
+ /** Optional language tags */
+ "langs"?: string[];
+}