Keeps oversized tool output out of the model's context: long shell and search results keep their head and tail with a visible truncation marker, and…

A local-first desktop app for building AI agents, connecting them to your tools, running them on triggers, and putting teams of them to work on real software projects. Credentials are encrypted, state lives in SQLite on your machine, and nothing leaves it unless you send it.
Built with Tauri 2 (Rust) and React 19 (TypeScript).
Jump to: What it does · Tour of the app · How a run works · Quickstart · Docs · Contributing · Changelog
Personas is built around one loop. You define agents (personas), give them connections to the services they need, let triggers run them, and watch and govern what they do. On top of that loop sit three larger things: a way to point teams of agents at your own codebases, three built-in Companions that look after the app itself, and plugins for adjacent jobs.
| If you want to… | Go to | Short version |
|---|---|---|
| Create an agent and tune its prompt, tools and model | Agents | Describe it in plain language; the app builds the spec |
| Give agents access to Slack, GitHub, a database… | Connections | Encrypted vault, 130+ built-in connectors |
| Run agents on a schedule, a webhook or an event | Events & Schedules | Ten ways a run can start, plus chains between agents |
| See what agents did, what it cost, and what needs you | Overview | Dashboard, approvals, incidents, memory |
| Point agents at your repos and steer them by goals and KPIs | Projects | Context map, goals, KPIs, task runner, fleet |
| Get help running all of the above | Companions | Athena, Overseer, Curator |
| Extend it | Plugins | Dev Tools, Obsidian Brain, Drive, Twin |
Why local-first? Your personas, prompts and credentials never leave your machine unless you explicitly send them. Outbound traffic goes only to the AI providers and third-party services you configure. No cloud account is required.
The sidebar has two levels: an icon rail of top-level sections and a panel of grouped pages inside each section. The sections below follow the rail from top to bottom, regrouped by the job each one does. Which sections you see depends on your interface tier and on whether you run a dev build.
Home is the entry point: a cockpit for what needs attention, a learning area, and guided tours that walk through first-run setup (first agent, first credential, first execution). Simple Mode is a reduced interface for non-technical users; see tiers.
Docs: home · onboarding · simple mode
Agents is where personas are created and edited. You describe what you want and the build session asks clarifying questions, then resolves a spec you can refine.
Docs: personas · templates · recipes · operations hub
Connections is the credential vault and everything around it.
Docs: connections · integrations
Events is the routing layer. Schedules (a calendar overlay from the title bar) holds cron jobs.
localhost:9420), an event on the bus, a polling check, a chain from another persona, a file watcher, the clipboard, app focus, or a composite of theseDocs: events & triggers · schedules · execution entry points · automation tools
Overview answers "what are my agents doing, and what needs me?"
| Group | Pages |
|---|---|
| Monitoring | Dashboard (Mission control: vitals, status monitor, leaderboard, self-healing), Activity (every execution with model, tokens and cost), Events |
| Operations | Approvals (manual-review queue), Incidents (one triage inbox over seven failure streams), Observability (alerts, health issues, trends, IPC performance), Messages |
| Memory | Memories (what agents remember, with dispute and recall controls) and Graph |
Also here: the Director, a built-in coach that scores each persona and suggests improvements, and the Persona Monitor, a full-screen grid of the whole fleet.
The runtime protects itself: per-run, daily and monthly budget limits, circuit breakers that disable an unhealthy provider, and a self-healing engine that detects and recovers from transient failures. Execution traces record spans, durations and cost.
Docs: overview · execution · persona monitor · Director
Projects turns the app from "run some agents" into "run a team on my repository." Register a codebase, map it, define what success means, and let agents work toward it.
| Group | What it is |
|---|---|
| Teams | Assemble a team from a preset, give it a goal, decompose, assign and run. Moderated multi-persona deliberations end in an approved assignment |
| Goals / KPIs | KPIs define outcomes; a KPI off its critical line derives goals; goals are what teams advance |
| Development | Lifecycle (each project's practice: Solo or Team), Factory (project readiness and KPI matrix), Contest (run several model seats on one brief and keep the winner), Mastermind (portfolio view across projects), Features, and Studio (an app builder, dev builds only) |
| Browser | Lets Athena and your personas open and control allow-listed web pages, with each write passing the approval orb |
The supporting tooling lives in Plugins → Dev Tools: Context Map (scan a repo into groups and contexts), Task Runner, Fleet (observe and steer many Claude Code CLI sessions in one window; dev builds), Workspaces (group projects into an org), and Skills. A Notepad (footer toggle) turns notes into dispatched work or goals.
An App Master is the accountable agent for one registered project. It can be adopted headlessly and driven from a terminal; see Headless bridge.
Docs: Teams & orchestration · Dev Tools · notepad · browser
Companions are three built-in agents, distinct from the personas you create. Each has its own pages and an on/off switch.
| Companion | Role |
|---|---|
| Athena | The assistant. Plans, builds, remembers and answers across the whole app, by chat or voice, with an orb overlay and guided walkthroughs |
| Overseer | Keeper of the fleet. Keeps starred agents running without error and worth what they cost; audits each wave of work. Needs at least one starred agent |
| Curator | Keeper of the knowledge registry. Keeps a mapped registry current and applied in the projects that subscribe to it. Needs a workspace with a registry |
Docs: companions · Athena · Curator
Enable plugins from Plugins. Each adds a group of pages to the sidebar.
| Plugin | What it gives you |
|---|---|
| Dev Tools | The codebase tooling described in section 6 |
| Obsidian Brain | A two-way bridge between agent memory and an Obsidian vault, as plain markdown, optionally backed up to your own Google Drive |
| Drive | A sandboxed local file manager for what agents, OCR, signing and exports produce |
| Twin | A digital identity (bio, per-channel tone, voice, curated memory) that your agents adopt so they speak as you |
Also in the tree: a built-in Scraper that emits change events onto the bus (dev builds), GitLab CI/CD export and deployment, and the Cloud orchestrator for pushing personas to a remote runtime.
Docs: plugins · Twin · Drive · Obsidian Brain · deployment · GitLab
Settings groups into General (account, appearance, data portability, radio, notifications), Connect (API keys for inbound MCP/HTTP access, paired devices, network sharing) and LLM (engine, custom models, limits). Data export and import can carry personas, connectors, twins and Athena's memory between machines; Sharing covers bundles, personas:// deep links and peer-to-peer transfer (needs a full build).
The desktop shell adds a system tray with scheduler pause/resume, native notifications, window-state persistence, a single-instance lock with deep-link routing, an in-footer Radio, and an auto-updater via GitHub Releases.
Docs: settings · sharing · radio · navigation
One codebase, three audiences. Each tier is a strict superset of the one before.
| Tier | Label in the app | Audience | Adds |
|---|---|---|---|
starter | Simple | Non-technical users | Agents, connections, messages, templates |
team (default) | Power | Teams and enterprises | Events, projects and teams, deployment, analytics, scheduling, databases, plugins, companions |
builder | (dev builds) | Developers | Dev-only surfaces, raw JSON editing |
Switch at runtime in Settings → Appearance → Interface Mode. Builder is a compile-time gate with no runtime path to it. To build a tier-locked bundle, see Build tiers.
You define a persona A trigger fires The provider returns
(prompt + tools + creds) (cron / webhook / event / structured output
| chain / file / clipboard) |
v | v
+--------------+ +------v-------+ +-------------+
| SQLite DB | ------------> | Rust engine | -------> | Post-process|
| (encrypted | load | (Tokio async)| parse | (healing, |
| credentials)| | spawns CLI | | chain, |
+--------------+ +--------------+ | notify) |
| +-------------+
Credentials injected
as env vars, scrubbed
after the run
| Data | Where it goes | Encryption |
|---|---|---|
| Persona prompts and config | Local SQLite | Plaintext (not secrets) |
| Credential values | Local SQLite | AES-256-GCM (key in OS keyring) |
| Execution output | Local SQLite + log files | Plaintext |
| AI provider requests | Provider API (HTTPS) | TLS in transit |
| Connector API calls | Third-party service (HTTPS) | TLS in transit |
| Error reports (opt-in) | Sentry | PII stripped before send |
| Layer | Technology |
|---|---|
| Desktop runtime | Tauri 2 |
| Backend | Rust, Tokio, SQLite (r2d2 pool) |
| Frontend | React 19, TypeScript 6, Vite 8 |
| Styling | Tailwind CSS 4 |
| State | Zustand 5 |
| Animation | Framer Motion |
| Visualization | Recharts, React Flow (XYFlow) |
| Encryption | AES-GCM, OS keyring |
| Networking | Reqwest (rustls-tls) |
| Dependency | Version | Check command |
|---|---|---|
| Node.js | >= 20 | node --version |
| Rust | via rustup; the repo pins its toolchain in rust-toolchain.toml | rustc --version |
| WebView2 Runtime | bundled with Windows 10+ | none |
| C++ Build Tools | MSVC (Visual Studio) | none |
winget install OpenJS.NodeJS.LTS
cargo and rustc are on your PATH: winget install Rustlang.Rustup
rustc --version
cargo --version
ring crypto crate): winget install LLVM.LLVM
Make sure C:\Program Files\LLVM\bin is on your PATH, and restart your terminal.
cl.exe, INCLUDE and LIB are set, or load the environment into the current session: & "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch arm64
xcode-select --install
brew install node
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Restart your terminal, then verify with node --version && rustc --version && cargo --version.
sudo apt update
sudo apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file \
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
# Node.js via NodeSource
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
npm install # 1. frontend dependencies
npm run tauri:dev:lite # 2. daily-driver dev mode (desktop features, no ML/P2P)
On Windows, the helper script avoids the usual startup problems (clang missing from PATH, a stale app process, a stale Vite process):
.\scripts\desktop-dev.ps1 -Restart # run the app
.\scripts\desktop-dev.ps1 -CheckOnly # preflight only
The first run compiles all Rust dependencies, which takes several minutes. Later builds are incremental.
Lite or full? Use tauri:dev:lite for almost everything: UI, IPC wiring, schema, triggers, recipes, observability. Switch to npm run tauri:dev (full) only when you are working on the vector knowledge base, embeddings, ONNX inference, or P2P, which are compiled only in the full build.
To build a tier-locked variant (higher-tier features are tree-shaken from the bundle):
npm run build:starter # Simple tier only
npm run build:team # Simple + Power
npm run build:builder # everything (default)
For desktop installers:
VITE_APP_TIER=starter npm run tauri build # Simple installer
VITE_APP_TIER=team npm run tauri build # Power installer
npm run tauri build # Builder installer (default)
When VITE_APP_TIER is unset, the build includes every tier and users switch at runtime.
| Command | What it does |
|---|---|
npm run dev | Vite dev server only (port 1420), frontend without Tauri |
npm run tauri:dev / :lite | Tauri dev mode, full or lite feature set |
npm run tauri:dev:test | Lite dev mode plus the test-automation HTTP server (:17320) and DevInspector |
npm run build | TypeScript check + Vite production build |
npm run tauri:build | Full installer (all targets, desktop-full) |
npm run tauri:build:lite | NSIS-only installer with desktop features |
npm run tauri:build:stable | NSIS + MSI installer for a Windows release |
npm run check | The full pre-push gate chain: project checks, type check, ESLint and the golden-path census |
npm run gate | Fast per-commit check (tsc, changed-file ESLint, census) from a warm local daemon |
npm run test | Vitest |
npm run test:rust | Rust unit tests (use this on Windows rather than raw cargo test) |
npm run check:tiers | Compile-check all three frontend tiers |
npm run analyze / check:budget | Bundle treemap / chunk-size budget |
npm run clean:rust / clean:ort | Full Rust wipe / surgical ONNX Runtime cache clean |
Build internals (ARM64 vs x64 on Windows, the codegen pipeline, profiles, ONNX bundling) are in docs/development/build.md; Android setup is in docs/development/android-build.md.
A dev-only overlay for grabbing a component's src/.../File.tsx:line and pasting it into an AI coding CLI. Off by default and never present in production builds.
npm run tauri:dev:test # full app + test bridge, source mapping on
npm run dev:inspect # frontend only, faster iteration
In the app press ; then i to arm it. Hover highlights an element and pins a path chip; click copies the call-site path, Alt+click copies the element, Esc exits. See docs/development/dev-inspector.md.
The running app exposes a loopback dev-tools bridge for creating projects, scanning codebases, adopting an App Master and writing results back, with no GUI. The full route reference is in docs/development/headless-bridge.md.
personas/
├── src/ # Frontend (React + TypeScript)
│ ├── api/ # Tauri IPC bridge (typed command wrappers)
│ ├── features/ # One folder per product surface
│ │ ├── agents/ personas/ # persona builder and editor
│ │ ├── templates/ # templates, recipes, design reviews
│ │ ├── vault/ # credentials and connectors (the "Connections" section)
│ │ ├── triggers/ schedules/# events, webhooks, cron
│ │ ├── overview/ # observability, approvals, incidents, memory
│ │ ├── teams/ # projects, teams, goals, KPIs, factory, contest
│ │ ├── companions/ # Athena, Overseer, Curator
│ │ ├── plugins/ # dev-tools, obsidian-brain, drive, twin, fleet, gitlab, radio
│ │ ├── browser/ studio/ notepad/ scraper/ cloud/ fleet/
│ │ └── home/ onboarding/ settings/ shared/
│ ├── i18n/ # Localization (locales, codegen, useTranslation)
│ ├── lib/ # Business logic; bindings/ holds Rust-generated TS types
│ ├── stores/ # Zustand state (slice pattern)
│ └── styles/ # Global CSS and themes
│
├── src-tauri/ # Backend (Rust)
│ ├── src/commands/ # Tauri command handlers (the IPC surface)
│ ├── src/engine/ # Execution engine, scheduler, healing, event bus
│ ├── db/ # SQLite schema, migrations, repos (extracted crate)
│ └── tauri.conf.json
│
├── docs/ # Feature, architecture, development docs
├── scripts/ # Codegen, census, i18n, templates, connector catalog
├── cloud-worker/ supabase/ sdk/ evals/ tests/
└── package.json
The system map, with the runtime layers and which command module owns what, is docs/architecture/overview.md and docs/architecture/codebase-map.md.
The app ships in 14 languages: English (source of truth) plus Arabic, Bengali, Czech, German, Spanish, French, Hindi, Indonesian, Japanese, Korean, Russian, Vietnamese and Simplified Chinese.
src/i18n/locales/en.json is the only file you edit for new strings. Each section of every locale is lazy-loaded as its own chunk, and a missing key falls back to English at runtime.src/i18n/section-locales/ and src/i18n/generated/types.ts are generated on predev / prebuild. Do not edit them by hand.hooks/register.ts 43 lines1import type { Register } from 'claude-code'
2
3import { economize } from './trim.mjs'
4
5// The model asked for these bytes exactly (a file window it will Edit against), so neither
6// truncation nor redaction may alter them.
7const EXACT = new Set(['Read', 'Edit', 'Write', 'NotebookEdit'])
8
9export const register: Register = on => {
10 on('session.append', { door: 'tool-result' }, async ($, e, next) => {
11 if (e.origin.kind === 'tool' && EXACT.has(String(e.origin.tool))) return next(e)
12
13 let changed = false
14 let dropped = 0
15 const content = e.message.content.map(block => {
16 if (block.type !== 'tool_result') return block
17 // tool_result content is a string or text blocks; anything else (images) passes through.
18 if (typeof block.content === 'string') {
19 const r = economize(block.content)
20 if (!r.changed) return block
21 changed = true
22 dropped += r.dropped
23 return { ...block, content: r.text }
24 }
25 if (!Array.isArray(block.content)) return block
26 const inner = block.content.map(part => {
27 if (part.type !== 'text') return part
28 const r = economize(part.text)
29 if (!r.changed) return part
30 changed = true
31 dropped += r.dropped
32 return { ...part, text: r.text }
33 })
34 return { ...block, content: inner }
35 })
36 if (!changed) return next(e)
37
38 if (dropped > 0) $.ui.status(`result-economy: trimmed ${dropped} lines`)
39 return next({ ...e, message: { ...e.message, content } })
40 // Fail open: a bug here must hand the model the original result, never lose one.
41 }).catch(($, e, next) => next(e))
42}
43hooks/trim.mjs 52 lines1// Pure text policy for result-economy: a tool result's text in, the text the model should read out.
2// No engine access; the hooks module and the tests both import this.
3
4export const LIMITS = { maxLines: 300, maxChars: 30000, head: 80, tail: 120, lineChars: 2000 }
5
6// Distinctive shapes only. A pattern loose enough to hit ordinary hex or base64 would
7// corrupt the text an Edit must match exactly, so each of these names its own prefix.
8const SECRETS = [
9 { kind: 'anthropic-key', re: /sk-ant-[A-Za-z0-9_-]{20,}/g },
10 { kind: 'github-token', re: /gh[pousr]_[A-Za-z0-9]{30,}/g },
11 { kind: 'aws-access-key', re: /\bAKIA[0-9A-Z]{16}\b/g },
12 { kind: 'private-key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
13 { kind: 'personas-local-token', re: /(x-personas-local-token:\s*)[0-9a-f]{32}/gi },
14]
15
16/** @returns {{ text: string, redacted: string[] }} */
17export function redact(text) {
18 const redacted = []
19 let out = text
20 for (const { kind, re } of SECRETS) {
21 out = out.replace(re, (m, lead) => {
22 redacted.push(kind)
23 return `${typeof lead === 'string' ? lead : ''}[redacted:${kind}]`
24 })
25 }
26 return { text: out, redacted }
27}
28
29/**
30 * Keeps the head and the tail of a long result: logs put their cause and their
31 * verdict at the two ends. Under the limits the text is returned untouched.
32 * @returns {{ text: string, dropped: number }}
33 */
34export function trim(text, limits = LIMITS) {
35 const capped = text.split('\n').map(l => (l.length > limits.lineChars ? `${l.slice(0, limits.lineChars)} [+${l.length - limits.lineChars} chars]` : l))
36 const lines = capped.length
37 const over = lines > limits.maxLines || text.length > limits.maxChars
38 if (!over) return { text: capped.join('\n'), dropped: 0 }
39 // Few but huge lines: nothing to drop by line count, so the per-line cap above already did the work.
40 if (lines <= limits.head + limits.tail) return { text: capped.join('\n'), dropped: 0 }
41 const dropped = lines - limits.head - limits.tail
42 const marker = `[result-economy:${dropped} of ${lines} lines omitted from the middle; narrow the command or read the file for them]`
43 return { text: [...capped.slice(0, limits.head), marker, ...capped.slice(lines - limits.tail)].join('\n'), dropped }
44}
45
46/** Redact first, so a token cannot survive in the kept head or tail, then trim. */
47export function economize(text) {
48 const r = redact(text)
49 const t = trim(r.text)
50 return { text: t.text, dropped: t.dropped, redacted: r.redacted, changed: t.dropped > 0 || r.redacted.length > 0 || t.text !== text }
51}
52