No description.

A local-first personal AI agent that runs on your own machine.
The shape of Claude Code, all-file storage (no DB · no account · no cloud), and a visual Workflow Studio.

Ginno turns the capabilities an AI agent should have into a complete desktop app, instead of scattering them across a terminal and a dozen browser tabs.
Full feature walkthrough:
docs/user-guide.md.
| Workflow Studio | Local knowledge base |
|---|---|
![]() | ![]() |
One design rule: your data should not leave your machine.
| Ginno | |
|---|---|
| Database | ❌ none — all state is JSON / JSONL / Markdown under ~/.ginno/ |
| Account | ❌ none — open it and go |
| Cloud sync | ❌ none — everything stays local |
| Portable | ✅ copy the folder to move it; readable, hand-editable, git-able |
You bring your own model key (OpenAI / Anthropic / DeepSeek, or any compatible endpoint). Ginno is just a client — it never takes custody of your key or your data.
Option A — download (macOS, Apple Silicon): grab the latest .dmg from Releases. It is not code-signed yet, so on first launch use right-click → Open to get past Gatekeeper.
Option B — build from source:
git clone https://github.com/robscc/ginno
cd ginno
pnpm install # web + desktop shell
cd packages/runtime && uv sync # Python runtime
cd ../..
pnpm dev # web + runtime + desktop shell, all at once
Requires: Node ≥ 20 · pnpm ≥ 9 · Python ≥ 3.11 (via uv) · Rust (Tauri).
Package a .app: make app → apps/desktop/target/release/bundle/dmg/Ginno_*.dmg (see docs/p3-packaging-notes.md).
| Layer | Choice |
|---|---|
| Shell | Tauri 2 (Rust) — process management + webview only |
| UI | Next.js 14 (static export) + React 18 + Tailwind |
| Runtime | Python + FastAPI + LangGraph (sidecar, bundled with PyInstaller) |
| Storage | plain files (no DB); optional LanceDB for semantic-search vectors |
Architecture and subsystem designs:
docs/architecture.md.
MIT © 2026 ChuanchuanSong
hooks/test-mod.ts 34 lines1// The `--test` fixture: one clean hook, one throwing hook, a renderer whose
2// buttons include a failing press callback, an op-invoking hook, and one
3// registration against an event this host never raises.
4
5export function register(on) {
6 // Clean: reads a `$` op and answers an object.
7 on('turn.start', async ($, e) => ({ id: await $.session.id(), turnId: e.turnId }))
8
9 // Throws: the report's failure row.
10 on('tool.call', () => {
11 throw new Error('exploded')
12 })
13
14 // Draws two buttons: one press resolves, one throws.
15 on('ui.render', async $ => {
16 const ui = $.ui.resolve()
17 return ui.Box({
18 children: [
19 ui.Button({ label: 'Go', onPress: () => 'ok' }),
20 ui.Button({ label: 'Break', onPress: () => { throw new Error('press exploded') } }),
21 ],
22 })
23 })
24
25 // Fire-and-forget `$` op for the ops section.
26 on('prompt.submit', async $ => {
27 $.ui.log('hi from test-mod')
28 return { text: 'rewritten' }
29 })
30
31 // Registered but never raised: a warning row, not a failure.
32 on('telemetry.mark', () => ({}))
33}
34