SwiftWork is a native macOS Agent workspace built with SwiftUI. Its core goal is to expose an Agent’s reasoning, tool calls, and execution results to users in real time. It solves the observability gap common in CLI-based Agents. Keywords: SwiftUI, Open Agent SDK, event-driven architecture.
The technical snapshot defines the project at a glance
| Parameter | Details |
|---|---|
| Project Name | SwiftWork |
| Language | Swift 6.1 |
| UI Framework | SwiftUI + @Observable |
| Data Flow Protocol | `AsyncStream |
| ` | |
| Persistence | SwiftData |
| Markdown Rendering | swift-markdown |
| Code Highlighting | Splash |
| Auto Update | Sparkle 2.x |
| Core Dependencies | Open Agent SDK, SwiftData, swift-markdown, Splash |
| GitHub Stars | Not provided in the source |
| Platform | macOS 14+ |
SwiftWork makes the Agent execution process visible
SwiftWork is not a simple chat wrapper. It is a native workspace designed around the Agent execution pipeline. After a user submits a prompt, the system returns an answer and progressively projects tool calls, file operations, command execution, and error messages onto a timeline.
This design addresses a common pain point in Agent products: users can see the output, but not the process. When a task involves multi-step reasoning, file I/O, and command execution, a linear terminal log does little to help users build a global understanding.
It fits complex Agent scenarios better than a CLI
A CLI is great for quickly validating SDK capabilities, but it is not good at expressing concurrent events, phase states, or the pairing between tool calls and tool results. SwiftWork uses a timeline, card-based tool views, and detail panels to turn process transparency into a first-class capability.
@Observable
final class AgentBridge {
var events: [AgentEvent] = [] // The UI consumes only a unified event model
var isRunning = false // Indicates whether the Agent is still running
}
This definition shows that SwiftWork exposes state changes directly to SwiftUI so the view layer can react and render automatically.
SwiftWork uses an event-driven, one-way data flow architecture
The key to this project is not the interface, but the data flow design. The end-to-end pipeline works like this: the Agent Loop outputs SDKMessage, the bridge layer consumes the stream and maps it into AgentEvent, the system then writes the result to memory and the database, and finally the timeline view renders it.
This one-way pipeline provides two major benefits. First, it reduces coupling between the SDK and the UI. Second, it keeps responsibilities clear at every layer, which makes it easier to replace SDK versions, extend event types, or refactor the renderer.
Four core components form the main pipeline
| Component | Responsibility |
|---|---|
| Agent Loop | Runs the reasoning loop and emits an SDKMessage stream |
| AgentBridge | Consumes the stream, manages lifecycle, and appends events |
| EventMapper | Maps SDK types into UI types |
| TimelineView | Renders the timeline from the event array |
func sendMessage(_ text: String) {
let userEvent = AgentEvent(type: .userMessage, content: text, timestamp: .now)
appendAndPersist(userEvent) // Write the user message first so it is visible immediately
isRunning = true
currentTask = Task { [weak self] in
let stream = agent.stream(text) // Get the streaming message source from the SDK
for await message in stream {
let event = EventMapper.map(message) // Convert it into a UI-consumable event
self?.appendAndPersist(event)
}
self?.isRunning = false // The task is complete, so clear the running state
}
}
This code reflects an interaction model of immediate feedback, then execution, then continuous streaming updates.
Decoupling the SDK from the UI is the most important design decision
SwiftWork does not let the view layer depend directly on SDKMessage. Instead, it defines its own AgentEvent as a UI domain model and keeps only the fields required for rendering, such as type, content, metadata, and timestamp.
That means internal SDK enums, field changes, or runtime details do not leak directly into the interface layer. For an Agent application that will evolve over time, this is a critical stability boundary.
EventMapper handles protocol translation
static func map(_ message: SDKMessage) -> AgentEvent {
switch message {
case .assistant(let data):
return AgentEvent(
type: .assistant,
content: data.text,
metadata: ["model": data.model], // Preserve context required by the UI
timestamp: .now
)
case .toolUse(let data):
return AgentEvent(
type: .toolUse,
content: data.toolName,
metadata: ["toolUseId": data.toolUseId, "input": data.input],
timestamp: .now
)
default:
return AgentEvent(type: .system, content: "Unhandled", timestamp: .now)
}
}
The value of this mapping logic lies in converting a runtime protocol into stable UI event semantics.
The persistence strategy centers on complete events rather than intermediate state
After an event enters the in-memory array, SwiftWork immediately attempts to write it to SwiftData. This approach satisfies both real-time rendering and session recovery, which makes it a strong fit for long-running tasks, resumable workflows, and historical auditing.
The source material specifically emphasizes that partialMessage is not persisted because it is only an intermediate fragment of a streaming response. The database stores only the aggregated, complete message. That avoids filling storage with meaningless fragments.
private func appendAndPersist(_ event: AgentEvent) {
events.append(event)
processToolContentMap(for: event) // Pair toolUse with toolResult
guard event.type != .partialMessage else { return } // Skip intermediate streaming fragments
try? eventStore?.persist(event, session: currentSession, order: eventOrder)
eventOrder += 1
}
This code shows that the project designs its event model around both recoverability and data hygiene.
The directory layering reflects a scalable engineering approach for Agent desktop apps
The project structure can be summarized in five layers: the app entry layer, the dual-model layer, the SDK integration layer, the view layer, and the service layer. In particular, the split between Models/UI and Models/SwiftData is worth noting. It is more robust than treating the UI model as the persistence model directly.
The SDKIntegration directory acts as the boundary layer for the entire project and shields the application from Open Agent SDK implementation details. Views/Workspace/Timeline carries the final visual expression, including event views, tool renderers, and the Inspector panel.
A simplified mental model of the directory structure
SwiftWork/
├── Models/UI/ # UI event models
├── Models/SwiftData/ # Persistence models
├── SDKIntegration/ # SDK bridge and mapping
├── Views/Workspace/ # Timeline and tool cards
└── Services/ # Markdown, Keychain, EventStore
This structure expresses a clean boundary between domain models, infrastructure, and rendering.
The technology choices optimize for native UX, concurrency safety, and readable content
Swift 6.1 strict concurrency, SwiftUI, and SwiftData form a cohesive Apple-native stack. For an Agent application that needs streaming event handling, automatic UI refresh, and local persistence, this combination provides clear advantages in both development efficiency and platform consistency.
For Markdown, the project uses Apple’s swift-markdown, and for code highlighting it uses Splash. This shows that the project also prioritizes native integration in content presentation rather than adopting a heavyweight cross-platform rendering solution.
The meaning behind the key technology stack
| Technology | Role |
|---|---|
| SwiftUI | Native desktop UI and reactive updates |
@Observable |
Simplifies state propagation |
| SwiftData | Persists sessions and events |
AsyncStream |
Carries streaming Agent output |
| Splash | Renders syntax highlighting |
This stack is not trend-chasing. It is a deliberate fit for the interaction density of a visual Agent workspace.
This project offers direct lessons for Agent product design
The real value of SwiftWork is not just that it built a desktop app with SwiftUI. It defines a minimal viable architecture for an Agent GUI: streamable, traceable, recoverable, and extensible.
If you are building an Agent product from an SDK upward, the first thing to design is usually not the prompt. It is the event model, the bridge layer, and the visualization structure. SwiftWork provides a clear engineering reference for exactly that.
FAQ
1. Why does SwiftWork define AgentEvent instead of using SDKMessage directly?
Because SDKMessage is designed for runtime use. Its fields are more complex and may evolve with the SDK. AgentEvent is designed for the UI and keeps only the information required for rendering, which significantly reduces coupling and improves interface stability.
2. Why does SwiftWork avoid persisting partialMessage?
Because it represents only an intermediate state in a streaming response. Persisting it would generate a large amount of fragmented data. The project chooses to save only fully aggregated events so historical records remain readable and recoverable.
3. What is the most valuable architectural idea in SwiftWork?
It is the combination of event-driven design, one-way data flow, and UI model decoupling. Together, these allow Agent reasoning, tool calls, and result presentation to enter the visualization system through one consistent mechanism.
Core Summary: SwiftWork is a native macOS Agent workspace built with SwiftUI and structured around the Open Agent SDK. This article breaks down its event-driven visualization model, one-way data flow, AgentBridge layer, event mapping strategy, SwiftData persistence, and tool card design.