A local-first asynchronous AI orchestrator that lives on your device. Vyotiq is the company; Agent V is the agent. Vyotiq is built around an unusual idea: the agent's behavior is governed by a natural-language harness — markdown files that act as the agent's operating system — not by hardcoded scripts.
- Plain-English harness, not a code-based one. Agent V reads its rules, loop, memory protocol, and tool catalogue from markdown files in
src/main/harness/. You can read them, change them, and the agent's behavior changes accordingly. Note: the harness is bundled into the main process at build time viaharnessLoader.ts(Vite?rawimports), not the renderer — editing the.mdfiles in a packaged build has no effect until the next rebuild. In a dev build (npm run dev) Vite's HMR picks up edits live. - Single dynamic agent. Agent V is one agent with a full tool surface (
bash,read,edit,search,ls,memory,recall, …). It plans, acts directly, and synthesizes answers in one context — no worker or delegation layer. - Memory across turns. Every turn appends typed events to a JSONL transcript. Each new send replays prior events into the OpenAI message shape so the agent remembers earlier user prompts and tool results. Older transcripts are normalized on load when needed.
- Provider-agnostic, no SDKs. Vyotiq talks raw OpenAI-compatible HTTP. It works with OpenAI, Anthropic-compat shims, Ollama, LM Studio, vLLM, Groq, Together, or any service that exposes
/v1/modelsand/v1/chat/completions. - Dynamic model discovery. When you add a provider, Vyotiq calls
GET /v1/modelsand populates the model dropdown automatically. - DeepSeek thinking-mode aware. Streamed
reasoning_contentis captured, persisted, and echoed back on the next request. The UI surfaces it as a collapsible "Thoughts" card. - Flexible turn endings. The agent may call
finishorask_user, or end with substantive prose when that fully answers the user (including short greetings and questions ending in?). Empty filler turns get one retry, then a visible error. - Structured logging. All main-process activity flows through a leveled logger with a rotating file at
<userData>/vyotiq/logs/vyotiq.log(1 MB / 3 backups). Renderer logs relay through the same file viavyotiq.log; crashes are caught by an error boundary and forwarded aterrorlevel. - Tailwind v4 CSS-first. No
tailwind.config.js. All design tokens live insrc/renderer/index.cssunder@themeand surface as utilities (bg-surface-base,text-text-muted, etc.). - Private by default. API keys are encrypted via your OS keychain (Electron
safeStorage). Web search is off by default and refuses non-HTTPS endpoints (except localhost). File operations are sandboxed to your active workspace.
- Electron (frameless, custom title bar,
contextBridge, sandboxed renderer) - React 19 + TypeScript 6 + Vite 7 (via
electron-vite 5) - Tailwind CSS v4 (CSS-first via
@theme) - Zustand (modular store slices)
- lucide-react (icons)
- fast-glob (local search + workspace listing)
npm install
npm run devThis launches Electron with hot-reload for the renderer and a watching build for main + preload.
To produce a production build:
npm run build
npm run preview- Pick a workspace. Use File → Open Workspace… or add a workspace tab in the left navigation dock. This is the folder Agent V's tools (
bash,ls,read,edit,search) will be sandboxed to. - Add a provider. Open Settings → Providers → "Add provider". Try a preset:
- OpenAI:
https://api.openai.com+ your key - Ollama (local):
http://localhost:11434(no key needed) - LM Studio (local):
http://localhost:1234
- OpenAI:
- Pick a model. The composer's model dropdown populates from
/v1/modelsautomatically. - Send a prompt. Try: "Survey this workspace and write a brief project map."
src/
├── main/ Electron main process
│ ├── harness/ ← Natural-language operating system (.md files)
│ ├── orchestrator/ AgentV runtime
│ │ ├── AgentV.ts Lifecycle: start/abort, replay seeding, error wiring
│ │ ├── loop/ Per-iteration phases (system prompt, assistant turn, tool dispatch)
│ │ ├── replay/ Reconstructs OpenAI messages from the JSONL transcript
│ │ ├── envelope/ XML escape helpers for host envelopes
│ │ ├── contextManager.ts Workspace + memory envelopes; per-iter refresh
│ │ ├── toolRunner.ts Tool dispatch (sandbox permissions, no approval modals)
│ │ └── retry.ts Exponential backoff with abort awareness
│ ├── tools/ One file per tool
│ │ └── policy/ AGENT_TOOLS allowlist
│ ├── providers/ Raw HTTP chat client + /v1/models discovery
│ ├── conversations/ Persistent JSONL transcript store + index
│ ├── memory/ Global meta-rules + per-workspace notes
│ ├── settings/ Shared settings blob (single writer)
│ ├── secrets/ safeStorage wrapper
│ ├── logging/ Centralized leveled + rotating-file logger
│ ├── ipc/ Typed IPC handlers (one per concern)
│ ├── window/ Frameless window factory
│ └── preload/ contextBridge → window.vyotiq
├── shared/ Types + constants used by both processes
└── renderer/ React frontend
├── components/ titlebar / composer / timeline / navigation / zone / settings / ui
│ ├── dock/ LeftDock (workspace tabs, chat strip, inline search)
│ ├── zone/ SecondaryZone + PanelFrame (settings)
│ └── titlebar/menu/ Modular File / Edit menu strip
├── store/ Zustand slices (chat / providers / conversations / settings /
│ workspace / ui / checkpoints)
├── pages/ ChatPage + ChatFooter shell
├── lib/ IPC wrapper, logger, helpers
├── styles/ Token documentation
└── index.css @theme tokens (Tailwind v4 CSS-first)
The agent's "operating system" lives in src/main/harness/ as a set of plain-English markdown files. They are concatenated and wrapped in <system_instructions> XML at runtime:
| File | Purpose |
|---|---|
00-orchestrator-core.md |
Prime directives and the agent loop (plan, act, verify). |
01-context-learning.md |
Context sources, authority order, memory protocol, offline research, continuous learning, and instruction hygiene. |
Per-tool briefs (WHAT/HOW/WHY/WHEN) are pulled directly from each tool's briefMarkdown field so they stay in sync with the OpenAI-compat schemas the model sees.
Each tool is in its own file (src/main/tools/<name>.tool.ts) and registered via registry.ts:
bash— cross-platform shell (PowerShell on Windows,/bin/bashelsewhere). Sandboxed cwd, destructive-pattern detection, timeout.ls— recursive directory listing. Default-ignoresnode_modules,.git,dist,out,.next.read— UTF-8 file reader with line range, 512 KB cap, binary refusal.edit— surgical exact-match edits + file creation. Returns diff stats for the FileEditCard.search— local grep across the workspace.memory— read/write/append global meta-rules or workspace notes.recall— read-only lookup against other conversations in the active workspace (orchestrator-only).
Agent V's allowlist is AGENT_TOOLS in src/main/tools/policy/agentTools.ts: bash, ls, read, edit, delete, search, memory, recall, finish, ask_user. The toolSchemasFor() helper filters schemas before each model request.
- Global meta-rules live in
<userData>/vyotiq/meta-rules.md. Loaded into<meta_rules>on every boot. - Workspace notes live in
<workspace>/.vyotiq/memory/*.md. Top-N relevant notes are injected into<recent_memory>at the start of each turn via keyword retrieval.
- API keys are encrypted via Electron
safeStorage(DPAPI on Windows, Keychain on macOS, libsecret/kwallet on Linux). Never written in plaintext. - All tool paths funnel through
src/main/tools/sandbox.ts. Path-escape attempts throw before reaching the filesystem. - A regex list of catastrophic patterns (
rm -rf /,format c:,git reset --hard, fork bombs,shutdown,mkfs,dd of=/dev/, write-redirection to absolute paths,teeto absolute paths, recursivechmodrooted at/) is checked insrc/main/tools/sandbox.tsbeforebashruns; matches return adestructive blockedtool result (no confirmation modal). - Web search sends only the user's query string. Never file contents, paths, or environment variables. Response bodies are stream-read with a 1 MB hard cap to prevent hostile / mis-configured endpoints from exhausting memory.
- The Chromium renderer runs in the OS sandbox (
webPreferences.sandbox: true) withcontextIsolation,nodeIntegration: false, andwill-navigate/will-attach-webviewguards. The CSP pinsscript-src 'self', blocksobject-src,frame-ancestors,form-action, and forbids<base>rewriting. - Production binaries should be hardened with
@electron/fusesvia the bundled script: after your packaging pipeline produces the binary, runnpm run flip-fuses -- path/to/Vyotiq.exeto disableELECTRON_RUN_AS_NODE,NODE_OPTIONS, and the V8 inspector args; enable ASAR integrity validation; and require the app to load only from the integrity-checked archive.
- Voice mic — no transcription engine bundled.
- Vector DB / semantic memory — keyword retrieval is sufficient for v1; documented as a swap point.
- Auto-update / code-signing pipeline — bring your own.
Private prototype. No license granted.