Skip to main content

Overview

Outputs are the results generated by agents executing tasks within orbits. OrbitAI provides a comprehensive output system that supports multiple formats, type-safe structured data, and rich metadata about execution.

Multiple Formats

Text, JSON, Markdown, CSV, XML, and structured

Type Safety

Strongly-typed structured outputs with Codable

Rich Metadata

Usage metrics, tool usage, timestamps

Fallback Parsing

Automatic error recovery for malformed outputs

Validation

Schema validation for structured data

Traceability

Track which agent produced which output

What are Outputs?

Outputs are the structured results from task execution that contain:
  • The actual generated content (raw or structured)
  • Metadata about execution (agent, task, timestamps)
  • Usage metrics (tokens, API calls)
  • Tool execution information
  • Validation results
Outputs provide complete traceability and observability into what agents produced and how they accomplished their tasks.

Key Characteristics

Outputs can be in various formats to suit different use cases:
// Plain text
let textOutput = TaskOutput(rawOutput: "Analysis complete")

// Structured JSON
let jsonOutput = TaskOutput(
    rawOutput: #"{"status": "success", "count": 42}"#,
    structuredOutput: parsedJSON
)

// Type-safe structure
let typedOutput = StructuredTaskOutput(
    data: AnalysisResult(findings: [...]),
    rawOutput: jsonString
)
Every output includes comprehensive metadata:
public struct TaskOutput {
    let rawOutput: String              // The actual content
    let usageMetrics: UsageMetrics    // Token usage
    let toolsUsed: [ToolUsage]        // Tool execution data
    let agentId: OrbitAIID            // Which agent produced this
    let taskId: OrbitAIID             // For which task
    let timestamp: Date               // When it was generated
}
Use Swift’s type system for compile-time safety:
struct Report: Codable, Sendable {
    let title: String
    let summary: String
    let findings: [Finding]
}

// Type-safe access
let report: Report = try output.decode(as: Report.self)
// Compiler ensures correct types
Automatic fallback strategies for parsing failures:
// Attempts multiple parsing strategies
let data = try output.decodeWithFallback(as: Report.self)

// Fallback order:
// 1. Direct decode
// 2. Unwrap common wrappers
// 3. Normalize field names
// 4. Partial extraction with defaults
// 5. Clean markdown artifacts

Output Types

TaskOutput

The result from a single task execution.
  • Structure
  • Creating
  • Accessing
  • Type-Safe Decoding
public struct TaskOutput: Codable, Sendable {
    // Core content
    public let rawOutput: String
    public let structuredOutput: StructuredOutput?

    // Execution metadata
    public let usageMetrics: UsageMetrics
    public let toolsUsed: [ToolUsage]

    // Traceability
    public let agentId: OrbitAIID
    public let taskId: OrbitAIID
    public let timestamp: Date

    // Validation (if applicable)
    public let validationResult: TaskValidationResult?
}
PropertyTypeDescription
rawOutputStringRaw text output from agent
structuredOutputStructuredOutput?Parsed structured data
usageMetricsUsageMetricsToken and API usage stats
toolsUsed[ToolUsage]Tools executed during task
agentIdOrbitAIIDAgent that produced output
taskIdOrbitAIIDTask that was executed
timestampDateWhen output was generated
validationResultTaskValidationResult?Manager validation result

OrbitOutput

The aggregated result from an entire orbit execution.
  • Structure
  • Accessing
  • Filtering Results
  • Exporting
public struct OrbitOutput: Codable, Sendable {
    // Task results
    public let taskOutputs: [TaskOutput]

    // Aggregated metrics
    public let usageMetrics: UsageMetrics

    // Execution timing
    public let executionTime: TimeInterval

    // Orbit metadata
    public let orbitId: OrbitAIID
    public let orbitName: String
    public let completedAt: Date

    // Process information
    public let processType: Process?
}
PropertyTypeDescription
taskOutputs[TaskOutput]All task results in order
usageMetricsUsageMetricsTotal usage across all tasks
executionTimeTimeIntervalTotal execution duration
orbitIdOrbitAIIDOrbit identifier
orbitNameStringHuman-readable orbit name
completedAtDateCompletion timestamp
processTypeProcess?Sequential/Hierarchical/etc

StructuredOutput

Parsed structured data with optional schema validation.
  • Structure
  • Creating
  • Validation
public struct StructuredOutput: Codable, Sendable {
    // Parsed data
    public let data: Metadata

    // Schema (if validated)
    public let schema: JSONSchema?

    // Validation result
    public let isValid: Bool
    public let validationErrors: [String]?

    // Original raw output
    public let rawJSON: String
}
PropertyTypeDescription
dataMetadataParsed structured data
schemaJSONSchema?Validation schema used
isValidBoolSchema validation passed
validationErrors[String]?Validation error messages
rawJSONStringOriginal JSON string

Output Formats

OrbitAI supports multiple output formats to suit different use cases.
  • Text
  • JSON
  • Markdown
  • CSV
  • XML
  • Structured
Format: Plain text output
let task = ORTask(
    description: "Summarize the article",
    expectedOutput: "Brief summary in plain text",
    outputFormat: .text  // Default
)

let result = try await orbit.start()
let output = result.taskOutputs.first!

// Access as string
print(output.rawOutput)
// "The article discusses AI trends in healthcare..."
Use Cases:
  • Human-readable reports
  • Summaries
  • Descriptions
  • General text generation

Structured Outputs

Comprehensive guide to creating and using type-safe structured outputs.

Creating Structured Outputs

1

Define Data Structure

Create a Codable struct representing your desired output:
struct AnalysisReport: Codable, Sendable {
    let title: String
    let executiveSummary: String
    let findings: [Finding]
    let recommendations: [Recommendation]
    let metadata: Metadata

    struct Finding: Codable, Sendable {
        let category: String
        let description: String
        let severity: Severity
        let evidence: [String]
    }

    struct Recommendation: Codable, Sendable {
        let title: String
        let description: String
        let priority: Priority
        let estimatedImpact: String
    }

    struct Metadata: Codable, Sendable {
        let analysisDate: Date
        let analyst: String
        let confidence: Double
        let version: String
    }

    enum Severity: String, Codable {
        case critical, high, medium, low
    }

    enum Priority: String, Codable {
        case urgent, high, medium, low
    }
}
Use nested types to organize complex data structures logically.
2

Create Task with Structured Output

Use the withStructuredOutput factory method:
let task = ORTask.withStructuredOutput(
    description: """
    Analyze the Q4 2024 business performance data and generate
    a comprehensive report with findings and recommendations.

    Focus on:
    - Revenue trends
    - Cost analysis
    - Market position
    - Growth opportunities
    """,
    expectedType: AnalysisReport.self,
    agent: analystAgent.id,
    context: [dataTask.id]
)
The system automatically:
  • Generates appropriate JSON schema from the type
  • Instructs the LLM to return structured JSON
  • Validates the output against the schema
  • Provides type-safe decoding
3

Execute and Access

Execute the orbit and decode the structured output:
let orbit = try await Orbit.create(
    name: "Business Analysis",
    agents: [analystAgent],
    tasks: [task]
)

let result = try await orbit.start()

// Type-safe decoding
if let output = result.taskOutputs.first {
    do {
        let report = try output.decode(as: AnalysisReport.self)

        // Access with full type safety
        print("Title: \(report.title)")
        print("Summary: \(report.executiveSummary)")

        print("\nFindings (\(report.findings.count)):")
        for finding in report.findings {
            print("- [\(finding.severity)] \(finding.category)")
            print("  \(finding.description)")
        }

        print("\nRecommendations:")
        for rec in report.recommendations {
            print("- [\(rec.priority)] \(rec.title)")
        }

        print("\nConfidence: \(report.metadata.confidence)")
    } catch {
        print("Decoding failed: \(error)")
        // Fall back to raw output
        print("Raw output: \(output.rawOutput)")
    }
}
4

Handle Decoding Errors

Implement fallback strategies:
// Try with automatic fallback
let report = try output.decodeWithFallback(as: AnalysisReport.self)

// Or handle explicitly
do {
    let report = try output.decode(as: AnalysisReport.self)
    processReport(report)
} catch DecodingError.keyNotFound(let key, _) {
    print("Missing key: \(key.stringValue)")
    // Try partial decode or use defaults
} catch DecodingError.typeMismatch(let type, let context) {
    print("Type mismatch for \(type) at \(context.codingPath)")
    // Try type coercion
} catch {
    print("Unexpected error: \(error)")
    // Fall back to raw output processing
}

Assigning Structured Outputs

  • Method 1: Factory
  • Method 2: Schema
  • Method 3: TypedJSON
// Using factory method (recommended)
let task = ORTask.withStructuredOutput(
    description: "Generate user profile data",
    expectedType: UserProfile.self,
    agent: agent.id
)
Benefits:
  • Automatic schema generation
  • Type-safe at compile time
  • Clean, readable API

Using Structured Outputs

struct Customer: Codable, Sendable {
    let id: UUID
    let name: String
    let email: String
    let phone: String
    let address: Address

    struct Address: Codable, Sendable {
        let street: String
        let city: String
        let state: String
        let zip: String
    }
}

// Generate customer data
let task = ORTask.withStructuredOutput(
    description: "Generate customer record from form data",
    expectedType: Customer.self,
    agent: dataAgent.id
)

let result = try await orbit.start()
let customer = try result.taskOutputs.first!.decode(as: Customer.self)

// Insert into database
try await database.insert(customer)
struct APIResponse: Codable, Sendable {
    let status: String
    let data: ResponseData
    let metadata: ResponseMetadata

    struct ResponseData: Codable, Sendable {
        let items: [Item]
        let total: Int
        let page: Int
    }

    struct ResponseMetadata: Codable, Sendable {
        let requestId: String
        let timestamp: Date
        let version: String
    }
}

// Generate API response
let task = ORTask.withStructuredOutput(
    description: "Format query results as API response",
    expectedType: APIResponse.self,
    agent: apiAgent.id
)

let result = try await orbit.start()
let response = try result.taskOutputs.first!.decode(as: APIResponse.self)

// Return as HTTP response
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try encoder.encode(response)
return Response(body: jsonData, contentType: .json)
struct DashboardData: Codable, Sendable {
    let title: String
    let widgets: [Widget]
    let refreshInterval: Int

    struct Widget: Codable, Sendable {
        let id: String
        let type: WidgetType
        let title: String
        let data: Metadata
        let position: Position

        enum WidgetType: String, Codable {
            case chart, table, metric, text
        }

        struct Position: Codable, Sendable {
            let x: Int
            let y: Int
            let width: Int
            let height: Int
        }
    }
}

// Generate dashboard configuration
let task = ORTask.withStructuredOutput(
    description: "Create dashboard layout for sales metrics",
    expectedType: DashboardData.self,
    agent: uiAgent.id
)

let result = try await orbit.start()
let dashboard = try result.taskOutputs.first!.decode(as: DashboardData.self)

// Render UI
await renderDashboard(dashboard)
// Task 1: Generate structured data
struct Analysis: Codable, Sendable {
    let insights: [String]
    let metrics: [String: Double]
}

let analysisTask = ORTask.withStructuredOutput(
    description: "Analyze data and generate insights",
    expectedType: Analysis.self,
    agent: analystAgent.id
)

// Task 2: Use structured output from Task 1
struct Report: Codable, Sendable {
    let summary: String
    let details: [Detail]
}

let reportTask = ORTask.withStructuredOutput(
    description: """
    Create report based on analysis: {task_0_output}
    """,
    expectedType: Report.self,
    agent: reportAgent.id,
    context: [analysisTask.id]
)

// Execute workflow
let result = try await orbit.start()

// Access both structured outputs
let analysis = try result.taskOutputs[0].decode(as: Analysis.self)
let report = try result.taskOutputs[1].decode(as: Report.self)

print("Insights: \(analysis.insights.count)")
print("Report sections: \(report.details.count)")

Fallback Parsing

OrbitAI includes sophisticated fallback strategies for handling malformed outputs:
1

Direct Decoding

First attempt: Standard JSONDecoder
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: jsonData)
2

Wrapper Unwrapping

Handle common wrapper patterns:
// LLM might wrap in: {"data": {...}}
let wrapperKeys = ["data", "result", "response", "content"]

for key in wrapperKeys {
    if let wrapped = json[key] as? [String: Any] {
        // Try decoding wrapped content
    }
}
3

Field Normalization

Map alternative field names:
// Handle snake_case vs camelCase
decoder.keyDecodingStrategy = .convertFromSnakeCase

// Custom key mapping
let normalized = normalizeFieldNames(json, for: T.self)
4

Partial Extraction

Extract available fields with defaults:
// If some fields missing, use defaults
struct PartialUser: Codable {
    let name: String
    let email: String
    let age: Int = 0  // Default
    let phone: String? = nil  // Optional
}
5

Markdown Cleaning

Remove markdown formatting artifacts:
let cleaned = jsonString
    .replacingOccurrences(of: "```json", with: "")
    .replacingOccurrences(of: "```", with: "")
    .trimmingCharacters(in: .whitespacesAndNewlines)

// Try parsing cleaned version

Error Handling

Common Output Errors

Missing Output

Error: Task completes but produces no output
do {
    let result = try await orbit.start()
    guard let output = result.taskOutputs.first else {
        throw OrbitAIError.taskExecutionFailed(
            "No output generated"
        )
    }
} catch {
    print("Error: \(error)")
}
Causes:
  • Task failed silently
  • Agent produced empty response
  • Output filtering removed content

Malformed JSON

Error: Cannot parse JSON output
do {
    let data = try output.decode(as: Report.self)
} catch DecodingError.dataCorrupted {
    print("Invalid JSON format")
    // Use fallback parsing
    let data = try output.decodeWithFallback(as: Report.self)
}
Causes:
  • LLM generated invalid JSON
  • Extra text before/after JSON
  • Unclosed brackets/quotes

Schema Mismatch

Error: Output doesn’t match expected structure
do {
    let data = try output.decode(as: Report.self)
} catch DecodingError.keyNotFound(let key, _) {
    print("Missing field: \(key.stringValue)")
} catch DecodingError.typeMismatch(let type, _) {
    print("Wrong type for field: \(type)")
}
Causes:
  • LLM misunderstood schema
  • Field name variations
  • Type differences

Empty Content

Error: Output exists but content is empty
let output = result.taskOutputs.first!

if output.rawOutput.isEmpty {
    print("Warning: Empty output")
    // Check if task failed
    if let task = orbit.tasks.first {
        print("Task status: \(task.status)")
    }
}
Causes:
  • Task execution issue
  • Agent configuration problem
  • LLM returned empty response

Error Recovery Strategies

  • Graceful Fallback
  • Retry with Clarification
  • Partial Success
  • Validation & Repair
func getReportData(output: TaskOutput) -> ReportData {
    // Try structured decode
    if let structured = try? output.decode(as: ReportData.self) {
        return structured
    }

    // Try fallback parsing
    if let fallback = try? output.decodeWithFallback(as: ReportData.self) {
        return fallback
    }

    // Parse raw output manually
    if let parsed = parseRawOutput(output.rawOutput) {
        return parsed
    }

    // Return minimal valid data
    return ReportData(
        title: "Error: Unable to parse report",
        summary: output.rawOutput,
        findings: [],
        recommendations: []
    )
}

Best Practices

Output Design

Clear Structure

Do: Define clear, well-organized structures
// Good: Logical organization
struct Report: Codable {
    let metadata: Metadata
    let content: Content
    let appendices: [Appendix]

    struct Metadata: Codable {
        let title: String
        let author: String
        let date: Date
    }
}

// Bad: Flat, disorganized
struct Report: Codable {
    let title: String
    let thing1: String
    let data: [String]
    let misc: String
}

Appropriate Types

Do: Use specific types
// Good
struct Product: Codable {
    let id: UUID
    let price: Decimal
    let inStock: Bool
    let category: Category

    enum Category: String, Codable {
        case electronics, clothing, books
    }
}

// Bad
struct Product: Codable {
    let id: String  // Should be UUID
    let price: String  // Should be number
    let inStock: String  // Should be Bool
    let category: String  // Should be enum
}

Optional vs Required

Do: Make intentional choices
struct User: Codable {
    // Required fields
    let id: UUID
    let email: String
    let name: String

    // Optional fields
    let phone: String?
    let bio: String?
    let avatar: URL?

    // With defaults
    let role: String = "user"
    let active: Bool = true
}

Documentation

Do: Document expected structures
/// Represents an analysis report with findings
/// and recommendations.
struct AnalysisReport: Codable {
    /// Report title (max 100 chars)
    let title: String

    /// Executive summary (200-500 words)
    let summary: String

    /// Detailed findings (3-10 items)
    let findings: [Finding]

    /// Actionable recommendations (min 2)
    let recommendations: [Recommendation]
}

Performance

1

Minimize Output Size

// Good: Concise outputs
struct Summary: Codable {
    let keyPoints: [String]  // Top 5 only
    let metrics: [String: Double]  // Essential metrics
}

// Avoid: Unnecessarily large outputs
struct VerboseSummary: Codable {
    let entireDocument: String  // Don't include full text
    let everyMetric: [String: Any]  // Don't include everything
}
2

Use Streaming for Large Outputs

// For large content generation
let stream = try await manager.generateStreamingCompletion(
    request: request
)

var fullOutput = ""
for try await chunk in stream {
    fullOutput += chunk.content ?? ""
    // Process incrementally
    await updateUI(chunk.content)
}
3

Cache Common Outputs

actor OutputCache {
    private var cache: [String: TaskOutput] = [:]

    func get(_ key: String) -> TaskOutput? {
        return cache[key]
    }

    func set(_ key: String, output: TaskOutput) {
        cache[key] = output
    }
}

// Use for repeated queries
let cacheKey = "\(task.description)-\(inputs.hashValue)"
if let cached = await cache.get(cacheKey) {
    return cached
}

Type Safety

struct Analysis: Codable {
    let status: Status
    let priority: Priority
    let category: Category

    enum Status: String, Codable {
        case pending, inProgress, completed, failed
    }

    enum Priority: String, Codable {
        case low, medium, high, critical
    }

    enum Category: String, Codable {
        case bug, feature, improvement, documentation
    }
}

// Type-safe access
if analysis.priority == .critical {
    // Handle critical priority
}
struct Order: Codable {
    let id: UUID
    let customer: Customer
    let items: [Item]
    let payment: Payment
    let shipping: Shipping

    struct Customer: Codable {
        let id: UUID
        let name: String
        let email: String
    }

    struct Item: Codable {
        let productId: UUID
        let quantity: Int
        let price: Decimal
    }

    struct Payment: Codable {
        let method: PaymentMethod
        let amount: Decimal
        let status: PaymentStatus

        enum PaymentMethod: String, Codable {
            case card, paypal, bankTransfer
        }

        enum PaymentStatus: String, Codable {
            case pending, completed, failed
        }
    }

    struct Shipping: Codable {
        let address: Address
        let method: ShippingMethod
        let tracking: String?

        struct Address: Codable {
            let street: String
            let city: String
            let state: String
            let zip: String
            let country: String
        }

        enum ShippingMethod: String, Codable {
            case standard, express, overnight
        }
    }
}

Troubleshooting

Symptoms: Task completes but no outputDebug Steps:
// Check task status
let tasks = await orbit.getTasks()
for task in tasks {
    print("Task: \(task.description)")
    print("Status: \(task.status)")
    if let result = task.result {
        switch result {
        case .success(let output):
            print("Output: \(output.rawOutput)")
        case .failure(let error):
            print("Error: \(error)")
        }
    }
}

// Check agent configuration
let agents = await orbit.getAgents()
for agent in agents {
    print("Agent: \(agent.role)")
    print("Tools: \(await agent.getToolNames())")
}
Solutions:
  • Verify agent has proper configuration
  • Check task description is clear
  • Enable verbose logging
  • Verify LLM provider is working
Symptoms: DecodingError when parsing outputDebug Steps:
// Print raw output
print("Raw output:")
print(output.rawOutput)

// Try parsing manually
if let jsonData = output.rawOutput.data(using: .utf8) {
    do {
        let json = try JSONSerialization.jsonObject(with: jsonData)
        print("Valid JSON:")
        print(json)
    } catch {
        print("Invalid JSON: \(error)")

        // Check for common issues
        if output.rawOutput.contains("```") {
            print("Contains markdown code blocks")
        }
        if !output.rawOutput.hasPrefix("{") && !output.rawOutput.hasPrefix("[") {
            print("Extra text before JSON")
        }
    }
}
Solutions:
// Use fallback parsing
let data = try output.decodeWithFallback(as: Report.self)

// Or clean manually
let cleaned = output.rawOutput
    .replacingOccurrences(of: "```json", with: "")
    .replacingOccurrences(of: "```", with: "")
    .trimmingCharacters(in: .whitespacesAndNewlines)

// Extract JSON from text
if let range = cleaned.range(of: #"\{[\s\S]*\}"#, options: .regularExpression) {
    let jsonOnly = String(cleaned[range])
    // Parse jsonOnly
}
Symptoms: Valid JSON but doesn’t match schemaDebug:
if let structured = output.structuredOutput {
    print("Valid: \(structured.isValid)")

    if !structured.isValid {
        print("Validation errors:")
        for error in structured.validationErrors ?? [] {
            print("  - \(error)")
        }
    }

    // Inspect actual structure
    print("Actual data:")
    printStructure(structured.data)
}

func printStructure(_ metadata: Metadata, indent: Int = 0) {
    let spacing = String(repeating: "  ", count: indent)

    switch metadata {
    case .dictionary(let dict):
        for (key, value) in dict {
            print("\(spacing)\(key): \(type(of: value))")
            printStructure(value, indent: indent + 1)
        }
    case .array(let items):
        print("\(spacing)Array(\(items.count) items)")
    default:
        print("\(spacing)\(metadata)")
    }
}
Solutions:
  • Adjust schema to match actual output
  • Provide clearer instructions to LLM
  • Use examples in task description
  • Lower validation strictness
Symptoms: DecodingError.typeMismatchCommon Causes:
// Expected Int, got String
struct Data: Codable {
    let count: Int
}
// LLM output: {"count": "42"}

// Expected Array, got single value
struct Data: Codable {
    let tags: [String]
}
// LLM output: {"tags": "tag1"}
Solutions:
// Use flexible types
struct FlexibleData: Codable {
    let count: FlexibleInt
    let tags: FlexibleArray<String>
}

// Custom decoding
struct Data: Codable {
    let count: Int

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // Try Int first, then String
        if let intValue = try? container.decode(Int.self, forKey: .count) {
            count = intValue
        } else if let stringValue = try? container.decode(String.self, forKey: .count),
                  let intValue = Int(stringValue) {
            count = intValue
        } else {
            count = 0  // Default
        }
    }
}
Symptoms: Output missing expected fieldsDebug:
do {
    let report = try output.decode(as: Report.self)
} catch DecodingError.keyNotFound(let key, let context) {
    print("Missing key: \(key.stringValue)")
    print("Context: \(context.codingPath)")
    print("Debug description: \(context.debugDescription)")

    // Check what's actually present
    if let json = try? JSONSerialization.jsonObject(
        with: output.rawOutput.data(using: .utf8)!
    ) as? [String: Any] {
        print("Available keys: \(json.keys)")
    }
}
Solutions:
// Make fields optional
struct Report: Codable {
    let title: String
    let summary: String
    let findings: [Finding]?  // Optional
    let recommendations: [Recommendation]?  // Optional
}

// Or provide defaults
struct Report: Codable {
    let title: String
    let summary: String
    let findings: [Finding] = []
    let recommendations: [Recommendation] = []
}

// Or decode partially
let partial = try output.decode(as: PartialReport.self)
let complete = partial.fillDefaults()

Next Steps

For additional support, consult the GitHub Discussions or check the Issue Tracker.