SLOPSHOPPER

result-economy

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…

newstatus
A shopper browsing a rack in a slop shop
README

Personas Desktop

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.

CI Release License: MIT Platforms Tauri 2 React 19 PRs Welcome

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


What it does

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 toShort version
Create an agent and tune its prompt, tools and modelAgentsDescribe it in plain language; the app builds the spec
Give agents access to Slack, GitHub, a database…ConnectionsEncrypted vault, 130+ built-in connectors
Run agents on a schedule, a webhook or an eventEvents & SchedulesTen ways a run can start, plus chains between agents
See what agents did, what it cost, and what needs youOverviewDashboard, approvals, incidents, memory
Point agents at your repos and steer them by goals and KPIsProjectsContext map, goals, KPIs, task runner, fleet
Get help running all of the aboveCompanionsAthena, Overseer, Curator
Extend itPluginsDev 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.


Tour of the app

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.

1. Start here

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

2. Build agents

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.

  • Persona editor with system prompt, tools, behavioural rules, model and reasoning effort, and a version history with diffs
  • Templates (under Connections → Templates): adopt a ready-made agent from the catalog, import one from n8n, or start from a generated draft
  • Recipes: reusable, parameterized capabilities you adopt onto a persona
  • Presets: pre-assembled teams of agents for a common job
  • Lab for testing a persona against scenarios, with quality scoring and prompt benchmarking
  • Chat tab as an operations hub: run, check health, edit prompts and assign tools without leaving the conversation

Docs: personas · templates · recipes · operations hub

3. Connect services

Connections is the credential vault and everything around it.

  • AES-256-GCM encryption at rest, with the key held in the OS keyring (Windows Credential Manager, macOS Keychain, Linux Secret Service)
  • 130+ built-in connectors (Slack, GitHub, Linear, Discord, Jira, Notion, Airtable, PostgreSQL, MongoDB, Stripe, Vercel and more), each with credential health checks and guided setup
  • Catalog, Databases, a Dependencies graph of what uses which credential, and a Broker
  • Credentials are handed to an execution as environment variables and scrubbed afterwards, never passed as CLI arguments

Docs: connections · integrations

4. Automate and trigger

Events is the routing layer. Schedules (a calendar overlay from the title bar) holds cron jobs.

  • A run can start from a manual click, a cron schedule, an inbound webhook (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 these
  • Chain Studio wires personas together by the signals they emit and listen for
  • Live Stream, speed limits (rate limits), dead-letter queue and a test panel for maintaining the event flow
  • Marketplace for shared events; automation tools (n8n, GitHub Actions) for outbound steps

Docs: events & triggers · schedules · execution entry points · automation tools

5. Observe and govern

Overview answers "what are my agents doing, and what needs me?"

GroupPages
MonitoringDashboard (Mission control: vitals, status monitor, leaderboard, self-healing), Activity (every execution with model, tokens and cost), Events
OperationsApprovals (manual-review queue), Incidents (one triage inbox over seven failure streams), Observability (alerts, health issues, trends, IPC performance), Messages
MemoryMemories (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

6. Ship software with agents

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.

GroupWhat it is
TeamsAssemble a team from a preset, give it a goal, decompose, assign and run. Moderated multi-persona deliberations end in an approved assignment
Goals / KPIsKPIs define outcomes; a KPI off its critical line derives goals; goals are what teams advance
DevelopmentLifecycle (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)
BrowserLets 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

7. Companions: the app's own agents

Companions are three built-in agents, distinct from the personas you create. Each has its own pages and an on/off switch.

CompanionRole
AthenaThe assistant. Plans, builds, remembers and answers across the whole app, by chat or voice, with an orb overlay and guided walkthroughs
OverseerKeeper 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
CuratorKeeper 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

8. Plugins

Enable plugins from Plugins. Each adds a group of pages to the sidebar.

PluginWhat it gives you
Dev ToolsThe codebase tooling described in section 6
Obsidian BrainA two-way bridge between agent memory and an Obsidian vault, as plain markdown, optionally backed up to your own Google Drive
DriveA sandboxed local file manager for what agents, OCR, signing and exports produce
TwinA 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

9. Settings and the desktop shell

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

Interface tiers

One codebase, three audiences. Each tier is a strict superset of the one before.

TierLabel in the appAudienceAdds
starterSimpleNon-technical usersAgents, connections, messages, templates
team (default)PowerTeams and enterprisesEvents, projects and teams, deployment, analytics, scheduling, databases, plugins, companions
builder(dev builds)DevelopersDev-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.


How a run works

 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
  1. Define. Create a persona, pick a model, assign tools, attach vault credentials.
  2. Trigger. Start it by hand, on a schedule, from a webhook or event, or from another persona.
  3. Execute. The Rust engine spawns the Claude Code CLI as a subprocess, streams its output, counts tokens and cost, and writes a trace. Custom or local model endpoints can be configured under Settings → Custom Models (dev builds).
  4. Observe. Executions land in Overview; approvals and incidents queue for you; healing retries what it safely can.
  5. Deploy (optional). Push personas to the cloud orchestrator, GitHub Actions, GitLab CI/CD, or n8n.

Data flow and privacy

DataWhere it goesEncryption
Persona prompts and configLocal SQLitePlaintext (not secrets)
Credential valuesLocal SQLiteAES-256-GCM (key in OS keyring)
Execution outputLocal SQLite + log filesPlaintext
AI provider requestsProvider API (HTTPS)TLS in transit
Connector API callsThird-party service (HTTPS)TLS in transit
Error reports (opt-in)SentryPII stripped before send

Tech stack

LayerTechnology
Desktop runtimeTauri 2
BackendRust, Tokio, SQLite (r2d2 pool)
FrontendReact 19, TypeScript 6, Vite 8
StylingTailwind CSS 4
StateZustand 5
AnimationFramer Motion
VisualizationRecharts, React Flow (XYFlow)
EncryptionAES-GCM, OS keyring
NetworkingReqwest (rustls-tls)

Prerequisites

DependencyVersionCheck command
Node.js>= 20node --version
Rustvia rustup; the repo pins its toolchain in rust-toolchain.tomlrustc --version
WebView2 Runtimebundled with Windows 10+none
C++ Build ToolsMSVC (Visual Studio)none

Windows setup

  1. Install Node.js:
   winget install OpenJS.NodeJS.LTS
  1. Install Rust via rustup, then restart your terminal so cargo and rustc are on your PATH:
   winget install Rustlang.Rustup
   rustc --version
   cargo --version
  1. Install Visual Studio C++ Build Tools: download Visual Studio Build Tools and select the "Desktop development with C++" workload. It provides the MSVC compiler and Windows SDK that Tauri needs.
  1. Install LLVM/Clang (required on Windows ARM64 for the ring crypto crate):
   winget install LLVM.LLVM

Make sure C:\Program Files\LLVM\bin is on your PATH, and restart your terminal.

  1. Run from a Developer shell. Launch your terminal from "Developer PowerShell for VS" so 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

macOS setup

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.

Linux setup (Debian/Ubuntu)

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"

Getting started

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.

Build tiers

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.

Common scripts

CommandWhat it does
npm run devVite dev server only (port 1420), frontend without Tauri
npm run tauri:dev / :liteTauri dev mode, full or lite feature set
npm run tauri:dev:testLite dev mode plus the test-automation HTTP server (:17320) and DevInspector
npm run buildTypeScript check + Vite production build
npm run tauri:buildFull installer (all targets, desktop-full)
npm run tauri:build:liteNSIS-only installer with desktop features
npm run tauri:build:stableNSIS + MSI installer for a Windows release
npm run checkThe full pre-push gate chain: project checks, type check, ESLint and the golden-path census
npm run gateFast per-commit check (tsc, changed-file ESLint, census) from a warm local daemon
npm run testVitest
npm run test:rustRust unit tests (use this on Windows rather than raw cargo test)
npm run check:tiersCompile-check all three frontend tiers
npm run analyze / check:budgetBundle treemap / chunk-size budget
npm run clean:rust / clean:ortFull 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.

DevInspector: click a component, copy its source path

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.

Driving the app from a terminal

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.


Repository layout

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.


Internationalization (i18n)

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.
Source 2 files
hooks/register.ts 43 lines
1import 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}
43
hooks/trim.mjs 52 lines
1// 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