-
-
Notifications
You must be signed in to change notification settings - Fork 41
Adding a Service
Services live under scarf/scarf/scarf/Core/Services/. They mediate between Hermes (filesystem, SQLite, subprocess) and feature ViewModels. The recipe is short.
| Pattern | When | Examples in repo |
|---|---|---|
actor |
The service owns mutable state across calls (a subprocess, file handles, an open SQLite connection, a snapshot dedup table). |
ACPClient, HermesDataService, HermesLogService. |
Sendable struct |
The service is stateless — every call re-reads through the transport. |
HermesFileService, HermesEnvService, ModelCatalogService. |
@MainActor @Observable |
Rare — only when the service IS UI-observable state (Sparkle wrapper). |
UpdaterService. |
If you're not sure, start with Sendable struct. Promote to actor only when you find yourself wanting to cache mutable state across calls.
import Foundation
struct MyHermesService: Sendable {
let context: ServerContext
private var transport: any ServerTransport { context.transport }
func loadSomething() async throws -> SomethingType {
let data = try await transport.readFile(context.paths.somethingFile)
return try JSONDecoder().decode(SomethingType.self, from: data)
}
func saveSomething(_ value: SomethingType) async throws {
let data = try JSONEncoder().encode(value)
try await transport.writeFile(context.paths.somethingFile, data: data)
}
}actor MyStatefulService {
let context: ServerContext
private var cache: [String: Value] = [:]
init(context: ServerContext) {
self.context = context
}
func get(_ key: String) async throws -> Value {
if let cached = cache[key] { return cached }
let value = try await fetchFromHermes(key: key)
cache[key] = value
return value
}
func invalidate() {
cache.removeAll()
}
private func fetchFromHermes(key: String) async throws -> Value {
// Use context.transport for I/O.
}
}-
Take
ServerContextininit. Never hardcodeServerContext.local— services must work against any window's bound server. -
Route I/O through
context.transportor thecontext.read*/write*/runHermeshelpers. Never useFileManager,Process, orNSWorkspace.opendirectly for Hermes paths — those break on remote (and break the rule from the project's feedback memory). -
Surface errors as
throwsorResult. Don't swallow them; the UI knows what to do with them. -
Don't log to
print— useos.Logger(logger.error()for unexpected,logger.warning()for expected). -
Don't do synchronous file I/O on
@MainActor. Either dispatch viaTask.detached { }.value, or expose async methods.
Two patterns:
Per-ViewModel (most common for stateless services):
@Observable
final class MyFeatureViewModel {
private let service: MyHermesService
init(context: ServerContext) {
self.service = MyHermesService(context: context)
}
}Shared via Environment (for stateful services that multiple features want to share):
In scarfApp.swift's ContextBoundRoot:
@State private var fileWatcher: HermesFileWatcher
ContentView()
.environment(fileWatcher)Then in any view:
@Environment(HermesFileWatcher.self) private var fileWatcherUse Environment for things every window has exactly one of — file watcher, server registry, updater service. Use per-ViewModel construction for everything else.
Service code that uses transport is testable with a mock transport. See Testing — what exists today is minimal, but the ServerTransport protocol is the obvious extension point for fakes.
Last updated: 2026-04-20 — Scarf v2.0.1
Wiki edited via the local .wiki-worktree/ clone. See Wiki Maintenance for the workflow. Last sync: 2026-04-20.
Getting Started
ScarfGo (iOS)
User Guide
- Dashboard
- Insights & Activity
- Chat
- Slash Commands
- Memory & Skills
- Projects & Profiles
- Project Templates
- Template Catalog
- Template Ideas
- Platforms / Personalities / Quick Commands
- Servers & Remote
- MCP, Plugins, Webhooks, Tools
- Gateway / Cron / Health / Logs
Architecture
- Overview
- Core Services
- Design System
- Data Model
- Transport Layer
- ScarfCore Package
- Sidebar & Navigation
- ACP Subprocess
Developer Guide
Reference
Troubleshooting
Contributing
Release History
Legal & Support
Unsorted