Changelog
All notable changes to OpenParallax are documented here. This project follows conventional commits and semantic versioning.
v0.1.1
Security
- IFC session taint — the engine tracks the highest sensitivity level seen per session. External actions (
send_email,http_request,send_message) with no file path are now checked against session taint, closing the gap where reading.envfollowed by sending an email was previously allowed. - IFC activity table — persistent SQLite table tracks classified write destinations across sessions. When the agent writes
.envcontent tonotes.txt, future reads ofnotes.txtinherit the classification. Prevents cross-session data laundering through intermediate files. - Content sensitivity tags — tool results carry sensitivity metadata through the gRPC protocol. Tags propagate through the LLM turn via
inherited_sensitivityon tool proposals, closing within-turn propagation gaps. - Memory write blocking —
memory_writeis separated into its own IFC sink category. Configurablememory_block_levels(in the IFC policy orconfig.yaml) block memory writes when session taint reaches specified sensitivity levels. Default:[critical, restricted]. - Sub-agent taint propagation — sub-agent session taint propagates back to the parent session. Sub-agents cannot be used to launder data past IFC.
CLI
openparallax ifc list— list all paths tracked in the IFC activity table with their sensitivity classifications and source paths.openparallax ifc sweep— remove activity table entries for files that no longer exist on disk. Logged asIFCSweepaudit events.
Documentation
- Rewrote IFC reference with session taint, activity table, content tags, memory sink, preset matrices, and CLI command documentation.
- Updated Security Architecture, Action Validation, Hardening Guide, CLI Commands, Configuration Guide, and Config Reference with IFC enhancements.
v0.1.0 — Initial Release
The first public release of OpenParallax. A reference implementation of the architecture described in Parallax: Why AI Agents That Think Must Never Act (PDF, arXiv forthcoming).
Architecture
- Agent-Engine separation — the Agent process owns the LLM session and runs inside a kernel sandbox. The Engine is the security gate that evaluates and executes every tool call. The Agent proposes actions but never executes them directly.
- Three-process model — Process Manager spawns Engine, Engine spawns sandboxed Agent. Clean restart via exit code 75. Crash recovery with budget.
- Single pipeline — all message processing routes through the Agent's bidirectional gRPC stream (RunSession). No fallback paths.
- gRPC services — AgentService (bidirectional streaming between Agent and Engine), ClientService (server streaming to CLI/web clients), SubAgentService (parallel task execution with follow-up messaging).
- EventBroadcaster — fan-out of 13 pipeline event types to all subscribed clients by session. Supports session-scoped and global subscriptions.
- Transport-neutral entry point — any channel adapter calls
ProcessMessageForWeb()with anEventSenderimplementation. - Modular engine — engine split across 5 focused files (engine.go, engine_pipeline.go, engine_grpc.go, engine_session.go, engine_tools.go).
- Platform abstraction — every OS-specific table, parser, and accessor lives behind build-tagged files in the
platform/package. Engine and Shield code makes zero runtime platform decisions of its own.
Security
Shield pipeline
- 4-tier Shield pipeline — Tier 0 (YAML policy matching), Tier 1 (ONNX DeBERTa classifier + curated heuristic ruleset), Tier 2 (LLM evaluator with canary verification), Tier 3 (human-in-the-loop approval via all connected channels). Fail-closed at every tier.
- Safe-command fast path — a curated allowlist of common dev workflow commands (
git,npm,make,go,cargo,docker,kubectl,pwd,whoami,date, etc., plus their cmd.exe equivalents on Windows) bypasses all four tiers and returns ALLOW with confidence 1.0. Single-statement commands only — anything containing;,&,|,>,<,`, or$(...)falls through to normal evaluation. The allowlist excludes commands that take arbitrary path arguments (cat,ls,grep,find,rm,cp,mv) so the heuristic and Tier 2 layers can still evaluate actual targets. Curated, ships in the binary, not user-extensible. Tables inplatform/safe_commands_{unix,windows}.go. - In-process ONNX classifier — DeBERTa v3 model runs in pure Go via
onnxruntime-purego. No sidecar processes, no CGo. Auto-resolves latest ONNX Runtime from GitHub releases. - Tier 1 heuristic engine — block / escalate / allow outcomes per rule. Hard-block patterns (curl-piped-to-shell, base64-piped-to-interpreter, reverse shells, credential directory reads, recursive chmod on system dirs, secret-env echo) return
VerdictBlock. Context-dependent patterns (rm -rf,&&chains,find -delete,git push --forceto main, crontab modifications, world-writable chmods,DROP TABLE, JWT handling, webhook destinations) escalate to Tier 2 via theEscalateflag instead of hard-blocking.AlwaysBlockandEscalateare mutually exclusive;NewHeuristicEngineskips any rule that sets both. False-positive-prone primitive rules (backticks,$(), heredocs, plaineval/exec, plainssh, plainnc, plainkill) are not in the rule set; the dangerous combinations of those primitives have their own dedicated rules. - Tier 2 evaluator with inverted context — independent LLM with no agent identity, no conversation history, no tools. Sees only the action type and payload as JSON data. Decides ALLOW / BLOCK / ESCALATE on action shape alone. ESCALATE routes to Tier 3.
- Canary tokens — random tokens generated during workspace init, embedded in evaluator prompt, verified in Tier 2 responses with constant-time comparison.
- SSRF protection —
SafeHTTPClientblocks private/loopback/link-local IPs at dial time with redirect re-validation (max 5 hops). HTTP and browser executors resolve hostnames before checking. Used byhttp_requestand Microsoft Graph calendar. - HTTP header denylist —
Authorization,Proxy-Authorization,Cookie,Set-Cookie, andHostheaders from LLM-supplied request headers are rejected at the executor level. - Git flag injection prevention —
--separator inserted before user-supplied filenames ingit addto prevent flag smuggling. - Tool call ID sanitization — characters outside
[a-zA-Z0-9_-]in tool call IDs are replaced with underscores, ensuring compatibility with Anthropic-backed OpenAI-compatible proxies. - Identity field validation —
identity.nameandidentity.avatarreject newlines, ANSI escapes, and control characters via^[a-zA-Z0-9 _-]{1,40}$to prevent prompt injection and terminal escape attacks. - Ollama base_url loopback restriction —
chat.base_urlmust point at loopback when the provider isollama(no api_key_env → no auth → exfiltration vector). - Setup wizard workspace allowlist —
POST /api/setup/completerejects workspace paths outside$HOMEor$OP_DATA_DIR. - Shield block reasons enriched — Tier 0 includes matched path and action type; Tier 1 heuristic includes the rule's human-readable description; Tier 1 ONNX explains the model verdict so the LLM understands why retrying won't help.
- Information Flow Control — 5 sensitivity levels with taint tracking. Prevents data exfiltration across sensitivity boundaries.
- Configurable output sanitization — opt-in wrapping of tool results and memory content in explicit data boundaries to mitigate prompt injection via untrusted content.
Cross-platform default denylist
A curated, ship-with-the-binary denylist applies to any path the agent touches, anywhere on disk — not just paths inside the workspace. Curated, not user-extensible. If a user wants the agent to access something on the list, they relocate the data to a path that is not on the list. Moving the file is the explicit consent action.
- Restricted (no read, no write): credential directories (
~/.ssh,~/.aws,~/.gnupg,~/.docker,~/.kube,~/.password-store,~/.azure,~/.config/gcloud,~/.config/op), Linux/etc/shadow,/etc/sudoers,/root,/etc/sudoers.d,/etc/ssh, macOS keychains and browser credential directories, WindowsC:\Windows\System32\configand credential vault. Filename patterns matched anywhere on disk:id_rsa/id_dsa/id_ecdsa/id_ed25519,*.pem/*.key/*.p12/*.pfx/*.keystore/*.jks/*.asc,.env/.env.local/.env.production,credentials.json,secrets.{yaml,yml,json},token.json,service-account.json,.pgpass,.my.cnf. - Protected (read OK, write/delete blocked): shell rc files (
.bashrc,.bash_profile,.zshrc,.zprofile,.profile, fish config), VCS and package manager configs (.gitconfig,.npmrc,.yarnrc,pip.conf, cargo config), editor configs (.vimrc, nvim init files,.tmux.conf), Linux system reference files (/etc/hosts,/etc/passwd,/etc/group,/etc/fstab,/etc/resolv.conf,/etc/crontab,/etc/environment), Linux cron/systemd/init/apt/yum directories, macOS/etc/hosts, Windows hosts file.
The denylist runs after symlink resolution. A symlink in /tmp/safe.txt pointing at ~/.ssh/id_rsa resolves to ~/.ssh/id_rsa and is blocked. Data tables live in platform/denylist_{linux,darwin,windows}.go behind build-tagged accessors. Engine code consumes them via cross-platform accessors and snapshots the lists at package init.
Workspace file protection
Files inside the agent's own workspace have a separate protection layer that runs after the cross-platform denylist:
- FullBlock —
config.yaml,canary.token,audit.jsonl,openparallax.db,.openparallax/,security/. Always blocked. The Tier 2 evaluator prompt is compiled into the binary and does not exist on disk — it cannot be tampered with at runtime. - ReadOnly — SOUL.md, IDENTITY.md,
skills/. Read OK, write/delete blocked. - EscalateTier2 — AGENTS.md, HEARTBEAT.md. Writes proceed but require Tier 2 LLM evaluation.
- WriteTier1Min — USER.md, MEMORY.md. Writes proceed but require Tier 1 minimum (heuristic/ONNX check).
Path enforcement
- Absolute paths required — every path argument to a tool must be absolute (or
~-prefixed for home expansion). Relative paths are rejected at the engine before Shield evaluation, with a clear error pointing the LLM at the requirement so it can re-roll on the next round. Shield evaluates the literal path string and cannot resolve relative paths against an implicit working directory; making path resolution unambiguous is what makes the denylist deterministic. - Shell
cd <abs> && cmdexemption — for shell commands, the one allowed exception is a leadingcd <absolute-path> && <command>prefix. The cd target establishes an implicit working directory, and write targets in the rest of the command are resolved against it. Anything else with relative paths is rejected.
Kernel sandboxing
- Kernel sandboxing — Landlock + seccomp-bpf (Linux 5.13+), sandbox-exec (macOS), Job Objects (Windows). Platform-specific canary probes verify enforcement at startup. The agent process has near-zero filesystem and network capability of its own — every action goes through the gRPC stream to the Engine, which is the policy authority. The seccomp filter allows
clone3withCLONE_THREAD(required by Go 1.22+ for OS thread creation) while blockingfork,vfork,execve, andexecveat.
Audit chain
- Tamper-evident audit — append-only JSONL with SHA-256 hash chain. Every tool proposal, execution, and Shield verdict logged. Chain verification via
openparallax audit --verify. - 24 audit event types:
ActionProposed,ActionEvaluated,ActionApproved,ActionBlocked,ActionExecuted,ActionFailed,ShieldError,CanaryVerified,CanaryMissing,RateLimitHit,BudgetExhausted,SelfProtection,TransactionBegin,TransactionCommit,TransactionRollback,IntegrityViolation,SessionStarted,SessionEnded,ConfigChanged,IFCClassified,ChronicleSnapshot,ChronicleSnapshotFailed,SandboxCanaryResult. The IFC classification, Chronicle snapshot success/failure, and sandbox canary result events ensure every security-relevant decision lands in the chain rather than just the structured engine log. - ConfigChanged audit emission — every slash-command-driven config mutation emits a
ConfigChangedaudit entry with source, changed keys, and SHA-256 hash diff of the previous and new file contents. - Sandbox canary plumbing — the sandboxed agent process cannot write to
audit.jsonlitself, so the JSON-encoded canary verification result rides on theAgentReadyproto event and the engine emits the audit entry on receipt.
Web and channel security
- Settings panel is read-only over HTTP — there is no
PUT /api/settingsendpoint. The web settings panel displays the current configuration as labels and values; to change a setting from the web UI, the user types/config set …in the chat input, which goes through the canonical writer the same way the CLI does. This eliminates the secret-exfiltration and Shield-disarm vectors that an HTTP write surface would expose. - Web security — bcrypt password auth, HttpOnly Secure SameSite=Strict cookies, WebSocket session authentication, login rate limiting (5/min per IP), 10MB message size limit, CORS restricted to configured origins (localhost-only default).
- Channel access control — Discord requires guild allowlist (DMs only when empty), Telegram defaults to private-chat-only with optional group allowlist.
Configuration
models[]androles{}schema — the workspace config defines a pool of provider+model entries inmodels[]and maps functional roles (chat,shield,embedding,sub_agent,image,video) to entries in that pool viaroles{}. There is no top-levelllm:key; the loader runs in strict YAML mode and rejects any unknown top-level field.- Single canonical writer —
config.Savemarshals viayaml.Marshal, writes atomically through<path>.tmp+ rename, re-loads throughLoad()to verify the round-trip succeeds, backs up the previous file to<workspace>/.openparallax/backups/config-<timestamp>.yaml(rotation: 100 most recent), emits aConfigChangedaudit entry on slash-command-driven saves, and rolls back on any failure. All four mutation surfaces (initCLI wizard, web setup wizard,/config set,/model) go through it. - Configurable timeouts and budgets —
agents.shell_timeout_seconds(30),agents.browser_nav_timeout_seconds(30),agents.browser_idle_minutes(5),agents.sub_agent_timeout_seconds(900),agents.max_concurrent_sub_agents(10),agents.crash_restart_budget(5),agents.crash_window_seconds(60),agents.max_consecutive_nav_failures(3). All with defaults, all initialized during init. SettableKeysregistry — single map enumerating every key writable through/config setand/model, with each key's optional validator, setter, andRequiresRestartflag. The slash command surface is the only consumer; there is no HTTP write surface. Identity values are validated against^[a-zA-Z0-9 _-]{1,40}$(newlines/escapes rejected); thechat.base_urlsetting is constrained to loopback when the chat model uses theollamaprovider; the setup wizard's workspace path must resolve under$HOMEor$OP_DATA_DIR.- Persistent slash commands —
/config setand/modelpersist toconfig.yamlthroughconfig.Savewith two-layer rollback on validation failure. Identity changes apply immediately on the live engine; model and provider changes require a restart to bind. - Read-only config keys — security-sensitive settings (
general.fail_closed,general.rate_limit,general.daily_budget,general.output_sanitization,agents.*pipeline parameters,shield.policy_file,shield.onnx_threshold,shield.heuristic_enabled,web.host,web.port,web.password_hash) cannot be changed via/config set. They must be edited directly inconfig.yamland require a restart.
Features
- 67 tool actions — file operations, git, shell commands, browser automation (Chromium-based browsers only, via CDP), email (SMTP + IMAP with full read/search/move/mark), calendar (Google + Microsoft + CalDAV), canvas, memory, HTTP requests, scheduling, clipboard, system info, open, notify, screenshot, image/video generation, sub-agents. Organized into 14 groups with dynamic loading via
load_toolsmeta-tool — loaded tools are merged into the LLM's active function-call schema on the fly. - Sub-agent orchestration — parallel task execution with isolated sandboxed processes. Follow-up messaging via
agent_message. Sub-agents receive a pre-resolved tool set (noload_tools— prevents deadlock) withagents,memory, andschedulegroups excluded. Auth tokens synced on every respawn so crash recovery works cleanly. - Reasoning visible in chat — the LLM's reasoning text is shown inline in the assistant bubble during and after streaming, separated by blank lines between fragments. Tool calls remain in the collapsible dropdown. Persisted on reload.
- Host environment detection — the agent checks USER.md for a "Host Environment" block before guessing OS, paths, or username. If empty, calls
system_info(host)to detect os, arch, username, home, shell, timezone, hostname and appends the result to USER.md so future sessions inherit it. - Per-tool system group detection —
system_infoalways registers; clipboard, notify, screenshot, and open register only when their underlying binaries are installed. The LLM never sees a tool it cannot call. - Browser consecutive failure cap — after
max_consecutive_nav_failures(default 3) consecutive navigation failures, the browser executor returns a clear error telling the LLM to fall back to other tools. Resets on success or session restart. - Memory flush pipeline — the compactor ships extracted facts to the engine via
EventMemoryFlushover gRPC. Sessions are summarized on everyResponseComplete, at engine startup (catch-up for unsummarized sessions from previous crashes), and at graceful shutdown. - LLM nudge for sub-agent delegation — the
agentstool group description, thecreate_agenttool description, and the agent's behavioral rules use a consistent vocabulary ("default for 2+ independent subtasks", "parallel", "cost", "clean context") to push the LLM toward delegating parallelizable work. Net cost ~60 tokens of static system context, amortized across every turn. - Numeric model index for sub-agents — the
modelparameter oncreate_agentis a 1-based integer index into a numbered menu rendered into the tool description from the workspacemodels[]pool. Out-of-range returns a graceful error so the LLM can recover on the next round. Optionalmodels[].purposeannotation per pool entry surfaces a hand-written hint ("fast, cheap, scans") into the menu; entries without one are still selectable. The pool snapshot is taken at engine startup so live config edits cannot drift the index mapping mid-session. - Sub-agent context isolation made explicit — the
taskparameter description spells out that the sub-agent starts with a blank context: it does NOT see the parent's conversation, files, or prior reasoning. The parent must include all background. - Custom skills — domain-specific guidance in
skills/<name>/SKILL.mdwith YAML frontmatter. Global skills at~/.openparallax/skills/, workspace skills override. Configurable disable list. - Multi-channel messaging — WhatsApp (Cloud API), Telegram (Bot API), Discord (bot), Slack (Socket Mode), Signal (signal-cli), Teams (Graph API), iMessage (AppleScript, macOS). Dynamic attach/detach at runtime.
- Web UI — glassmorphism two-panel layout (sidebar + chat). Drag-to-resize, responsive breakpoints (full/compact/mobile), real-time streaming via WebSocket. Console split into Metrics, Live Logs, and Audit views for security tuning and diagnostics. Read-only settings panel showing the config.yaml path for direct editing. Pipeline errors render as system messages in the chat. OTR-blocked tool calls complete visually. Session search returns per-message results with click-to-scroll-to-message. Context menu positions correctly regardless of CSS transforms.
- CLI — Cobra + Bubbletea TUI. Shell commands:
start,init,stop,restart,status,doctor,attach,detach,delete,list,config,session,memory,logs,audit,skill,mcp,get-vector-ext,chronicle,auth. Slash commands (19 total, in-session):/help,/new,/otr,/quit,/clear,/sessions,/switch,/delete,/title,/history,/export,/status,/usage,/doctor,/audit,/config,/model,/restart,/logs. - OTR mode — off-the-record sessions with read-only tools, no memory persistence, amber UI accents, data in
sync.Mapinstead of SQLite. - Semantic memory — FTS5 full-text search + vector embeddings. sqlite-vec for native in-database vector queries (auto-downloads latest). Embedding cache with content hashing for skip-unchanged indexing. File watcher for automatic reindexing.
- Chronicle — copy-on-write workspace snapshots before every write/delete/move. Hash-chained integrity. Configurable retention. Rollback to any previous state.
- MCP integration — connect to external MCP tool servers via stdio transport. MCP tools registered as loadable groups. All MCP tool calls pass through Shield. Idle shutdown with automatic reconnect.
- Context efficiency — dynamic tool loading, markdown stripping from system prompts, stale tool result summarization, configurable compaction threshold (default 70%), memoized markdown rendering in frontend.
- Configurable pipeline —
max_tool_rounds(25),context_window(128000),compaction_threshold(70% — controls both the trigger and the compactor's history/current-turn split),max_response_tokens(4096),shell_timeout_seconds(30),browser_nav_timeout_seconds(30),browser_idle_minutes(5),sub_agent_timeout_seconds(900),max_concurrent_sub_agents(10),crash_restart_budget(5),crash_window_seconds(60),max_consecutive_nav_failures(3) — all adjustable via config.yaml. - E2E test suite — full engine + agent integration tests with three LLM modes: mock (deterministic, CI), Ollama (local model), and cloud (real provider). 13 tests covering boot, message pipeline, Shield block, OTR, sub-agent lifecycle, chronicle rollback, audit chain integrity, and agent survival after blocked actions. Build-tagged
e2eso they never run inmake test. - Session management — auto-generated titles after 3+ exchanges. Heartbeat sessions for scheduled tasks. Session search across message content.
- Token usage tracking — per-session and per-message LLM usage with daily aggregation. 90-day retention policy with automatic archival.
- Per-tier Shield latency metrics —
metrics_latencytable stores one sample per Shield evaluation, tagged by tier.GET /api/metricsexposes per-tiershield_t{0,1,2}_p{50,95}_mspercentiles in theperformanceblock. Sourced from observation samples, not the audit chain — audit stays clean of performance telemetry. - System prompt templates — token-efficient identity, guardrails, and behavioral rules. ~250 tokens for the full static context.
- 14-point health check —
openparallax doctorverifies config, the canonical writer round-trip, workspace, SQLite, LLM provider, Shield, embedding, browser, email, calendar, HEARTBEAT, audit (chain integrity), sandbox, and web UI.
Composable Modules
Every module ships as an independently importable Go package with no dependencies on the rest of OpenParallax. Cross-language support via JSON-RPC bridge binaries for Python and Node.js:
- Shield — 4-tier AI security pipeline. Available as Go library, Python/Node wrappers, standalone MCP proxy.
- Memory — semantic memory with pluggable backends: SQLite (default), PostgreSQL + pgvector, Qdrant, Pinecone, Weaviate, ChromaDB, Redis.
- Audit — tamper-evident hash chain logging for any system.
- Sandbox — kernel-level process isolation for any child process.
- Channels — multi-platform messaging adapters.
- Chronicle — copy-on-write file versioning with rollback.
- LLM — unified provider abstraction (Anthropic, OpenAI + compatible APIs, Google, Ollama).
- IFC — information flow control with sensitivity labels and taint tracking.
- Crypto — ID generation, action hashing, hash chains, canary tokens, AES-256-GCM encryption.
- MCP — Model Context Protocol client integration.
- Platform — OS detection, shell configuration, sensitive paths, denylist tables, safe-command allowlist, cross-platform shell parser. All OS-conditional code lives here behind build-tagged files; consumers make zero runtime platform decisions.
Infrastructure
- Zero CGo — single static binary with
CGO_ENABLED=0. Pure Go SQLite (modernc.org/sqlite), pure Go ONNX Runtime (onnxruntime-purego). No C compiler needed. - Cross-platform — Linux, macOS, and Windows. Platform-specific code uses build tags exclusively; no
runtime.GOOSbranches outside theplatform/package. - Embedded web UI — Svelte 4 + Vite 5 frontend bundled into the Go binary via
go:embed. - Centralized model defaults — all LLM model names in a single file (models.go). Auto-resolves latest sqlite-vec and ONNX Runtime versions from GitHub releases.
- CI/CD — cross-platform binary builds for 6 OS/arch combinations. 7 binaries per release (agent, shield, 5 bridge binaries).
- Dynamic port allocation — each agent gets a unique port from the registry with gap scanning. The engine writes the actual bound gRPC port to
engine.portsoattachalways finds the live engine even after a port-conflict fallback.
Documentation
- VitePress documentation site — dark theme with module accent colors.
- 4 design philosophy pages — Defense in Depth, Process Isolation, Token Economy, Modularity.
- User guide — installation, quickstart, configuration, CLI, web UI, sessions, memory, skills, tools, channels, security, heartbeat, troubleshooting.
- Technical docs — architecture, process model, message pipeline, ecosystem, engine, agent, gRPC, events, protection, web server, extending.
- Module docs — Shield (14 pages including Tier 3), Memory, Audit, Sandbox, Channels (7 platforms), Chronicle, LLM, IFC, Crypto, MCP.
- API reference — config schema, environment variables, 24 audit event types, 69 action types, gRPC API, REST endpoints, WebSocket protocol, policy syntax.
See the Roadmap for planned features.