Engineering Workflow
August 12, 2026 · View on GitHub
Contributor-facing document. Coding agents should still start from AGENTS.md; human contributors should start from CONTRIBUTING.
This document holds the engineering conventions that do not need to live in every agent startup context.
Repository Map
galley/
├── README.md
├── README.zh-CN.md
├── AGENTS.md
├── runner/ # Python bridge into GenericAgent
├── core/ # Rust Galley Core + Tauri backend
├── cli/ # Rust `galley` CLI
├── gui/ # React / Tauri frontend
├── managed-ga/ # Galley-managed GenericAgent runtime (code, patches, manifest)
├── scripts/ # Build, bundle, and release / update-channel scripts
├── docs/ # Product, architecture, workflow, devlog
└── .github/workflows/ # CI and release workflows
Key directories:
runner/: Python bridge, GenericAgent handler subclass, IPC dataclasses, runner tests.core/: Rust authoritative layer, SQLite migrations, Tauri commands, socket / named pipe listener, bundled resources.cli/: Agent-facinggalleycommand.gui/: React 19 + Tauri frontend, Zustand domain stores, visual components.managed-ga/: Galley-managed GenericAgent runtime — vendored code, Galley patches, and the runtime manifest.scripts/: build, bundle, and release / update-channel automation.docs/devlog/: decision provenance and historical narrative.docs/archive/: completed-mission docs kept for provenance (B-phase refactor playbooks, one-off handoffs, superseded drafts).
Common Commands
From repo root unless noted. The Cargo workspace root is core/ (there is no
root Cargo.toml), hence --manifest-path:
cargo check --manifest-path core/Cargo.toml --workspace
cargo test --manifest-path core/Cargo.toml --workspace
pnpm --dir gui typecheck
pnpm --dir gui lint
pnpm --dir gui build
pnpm --dir gui tauri dev
pnpm --dir gui tauri build
git diff --check
Inside gui/, the shorter forms also work:
pnpm typecheck
pnpm lint
pnpm tauri dev
pnpm tauri build
pnpm tauri dev is the normal desktop dogfood command. It runs the client app,
not a web-only experience.
Avoid opening the Vite-only app in a browser for Settings, updater, IPC,
database, menu/tray, or other Tauri-dependent flows. The page lacks the Tauri
runtime, so invoke / listen / plugin APIs fail with expected errors and do
not provide useful GUI verification. Use static checks for fast feedback, and
use pnpm --dir gui tauri dev when the rendered desktop surface matters.
Python Runner Rules
- Python 3.10+.
- Type annotations are expected.
- Keep runner code independent from GUI.
- Do not introduce third-party packages beyond GenericAgent dependencies unless the need is clear.
- Cover IPC schema, hook behavior, and subprocess isolation in
runner/tests/.
TypeScript / GUI Rules
Toolchain:
- Tauri v2
- Vite 7
- React 19
- TypeScript strict
- Tailwind v4 CSS-first tokens in
gui/src/styles/globals.css - Phosphor Icons as the product icon set
- Self-hosted fonts through npm packages
- pnpm only
Component guidance:
- Use shadcn/Radix-style primitives for standard accessible behaviors: dialog, dropdown menu, popover, tabs, tooltip, command, input, button.
- Use local product components for domain-specific surfaces: Sidebar, Composer, Tool callouts, Approval Dock, Health Check, Onboarding, Empty State.
- Keep UI state in the relevant domain store. The old monolithic
useAppStore.tsno longer exists.
Design references:
Hard Engineering Invariants
Most of these rules were forged during the B-phase refactor and all remain binding after v0.2.0; later additions carry their own provenance inline. Violating one is grounds for revert. The IDs are stable because devlog entries, playbooks, and commit messages reference them; the retired refactor-execution rules (I1, I2, I4, I7, I8, I10) live in the archived invariants file.
- I3 — SQLite migration numbering. Migrations increment by commit order.
Never skip, reuse, or edit a shipped migration — dogfood databases have
already run that number, and editing it makes the two sides drift. A wrong
migration is fixed by adding a new one on top.
Adding the
.sqlfile is not enough — the runtime does not scan the directory. A new migration must also be registered in BOTH runtime lists: theMigrationvec incore/src/db_migrations.rsandMIGRATION_SPECSincore/src/migration_backup.rs(plus its latest-version test assertions), or the shipped app silently never applies it and every query touching the new column fails (2026-07-20: sidebar went empty in dogfood exactly this way). Test fixtures keep their own per-file migration lists (core/tests/*.rs,cli/tests/*.rs) — extend the ones whose tables you touched. - I5 — API surface single source of truth. The Rust
GalleyApitrait is the only definition of commands. Both transports (Tauri command, socket) thin-wrap it. No command may exist on only one transport, and no business logic may live in a transport layer — protocol adaptation (framing, event emit) only. - I6 — GUI stays a stateless presenter.
gui/never reads or writes SQLite directly, never owns runner subprocesses, and never holds authoritative state. The test: if the CLI performed the same action, would the React store's value now be wrong? If yes, that state belongs in Rust. - I9 — Shipped data formats are contracts. Once a SQLite schema, prefs format, or file location ships, changes are additive only: new columns with compatible defaults, new tables, new prefs keys. Never delete a shipped column, change its semantics, or move data without a real migration.
- I11 — Keep
panic = "unwind". Bridge children are reclaimed bykill_on_drop(true)during unwind.panic = "abort"skips Drop and orphans every live bridge process. No[profile.*] panic = "abort"incore/Cargo.toml, ever — the ~5% binary-size saving is not worth it. - I12 — Prune before descending when walking user directories. Any
traversal of a user-chosen directory (artifact scans, project discovery,
file pickers) must filter directories before entering them — Rust
walkdirwithfilter_entry, never a glob-style descend-then-filter. On macOS 14+ merely listing another app's data directory (containers under~/Library/Application Support) trips the TCC "access data from other apps" prompt; a recursive-glob walk of a home-directory workspace fires that alarming system dialog on every refresh. Always exclude dot-directories and OS data dirs at the prune step. (Adopted 2026-08-07 from a verified OpenWorker incident + fix — their regression test spies on the walk to assert~/Libraryis never entered.)
Code-level proofs and grep gates for the architecture-layer principles are in architecture demo.
IPC Protocol Changes
Protocol is a contract. Change docs first, then code.
- Update ipc-protocol.
- Update Python IPC dataclasses in
runner/ipc.pywhen runner protocol is affected. - Update TypeScript mirror types in
gui/src/types/ipc.tswhen GUI protocol is affected. - Update Rust command / event types when Galley Core protocol is affected.
- Add or adjust tests in the same change.
For Agent-facing CLI JSON, read agent-api. That surface is more stable than internal GUI IPC.
Git Discipline
- Preserve user or other-agent work in a dirty tree.
- Keep commits independently working when the user asks for commits.
- Use English commit messages that explain intent.
- Do not push unless explicitly asked.
- Keep baseline upgrades as separate commits when possible.
Devlog Workflow
Use devlog for decision provenance:
- major architecture or product decisions
- meaningful rejected alternatives
- phase changes
- release or dogfood retrospectives
File naming:
docs/devlog/YYYY-MM-DD-topic-in-kebab-case.md
Each entry should cover:
- Date / Status / Related
- Context
- Decisions
- Rejected alternatives
- Open questions
- Next
After writing a devlog, update devlog README.