Architecture — explanation
What is this about?
Before writing the application, the project fixed how its code is divided, what is stored where, and how the Go side informs the interface. This page tells the story; the precise package list, data model, storage keys and event catalogue are in ADR 0003.

The diagram's source is packages.drawio. Blue boxes exist since milestone M1; grey ones are planned.
Why this split
Three facts drove it:
- An agent is shared. Alice may be in three channels at once, but she is one process with one model in memory. So one package owns the agent processes (
internal/agents) and another owns channels and routing (internal/channels); a channel asks for "a session with Alice", it never starts her. - The interface must not know ACP. If the Svelte code handled ACP messages, changing protocol version or agent would ripple into the interface. Everything the interface sees goes through
internal/app, as Sidekicks' own calls and events. - The configuration can change while agents run. It is loaded, validated and watched in one place (
internal/config), which tells the others.
Where Wails stops
Only main.go, at the root of the repository, knows Wails: it creates the window, binds the services of internal/app and plugs the Wails event manager in. Everything below it is plain Go that emits through a small Emitter interface, so the agent manager, the configuration and the services are tested without a window. The main package sits at the root rather than in cmd/ because the build files Wails generates expect it there; fighting the tool would cost more than it gives.
What lives in the YAML and what lives in the database
The YAML file describes what exists: agents, limits, preferences, and channels to create. It is meant to be read and edited by a person. What happens — the channels created from the interface, who joined them, the messages — is stored by the application in Pebble, an embedded key-value store. A channel declared in YAML is created once at startup and then lives in the database like any other, so that editing the YAML never deletes a conversation by accident.
Each configuration file gets its own database: switching from "my work agents" to "my writing agents" never mixes their histories.
Messages are written once, at the end
While an agent answers, the answer lives in memory and the interface receives each fragment. It is written to disk when the turn ends. Writing fifty times a second would wear the disk for nothing; the price is losing an unfinished answer if the application crashes, which is acceptable.
Rejected alternatives
- Storing conversations in the YAML file: it would turn a file people edit into a database, and conflict with the application writing to it.
- One package per Wails service with logic inside: it would mix interface concerns with the domain and make it untestable without a window.
How it relates to the rest
The protocol side is explained in Why ACP, the interface side in The interface stack, and the file format is described in the configuration reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|