Skip to content

Latest commit

 

History

History
693 lines (514 loc) · 31.1 KB

File metadata and controls

693 lines (514 loc) · 31.1 KB

Advanced Features

Sub-Agent Spawning

The LLM can use the Spawn tool to create independent sub-agents that run tasks in parallel. Each sub-agent has its own conversation context and shares the parent agent's LLM provider (connection pool reuse).

A sub-agent does not get your tool set. Spawn children are read-only. Every Spawn dispatch builds the child registry with an empty request, which falls to the read-only floor — Read, Grep and Glob, and nothing else (SHARED_READ_ONLY_CHILD_TOOLS, crates/wcore-types/src/spawner.rs:159). Write, Edit and Bash are never registered on a Spawn child, whatever the parent holds.

That floor is then intersected with the parent's own authority, so a child can only ever be a subset of the session that spawned it — a parent that has had Grep and Glob dropped by its posture cannot recover them by spawning (crates/wcore-agent/src/spawner.rs:2818). The intersection is unconditional: there is no "no authority supplied" arm to fall through.

The child's filesystem access is also jailed to a contained workspace, with the parent's read/write deny list applied on top.

Use Cases

  • "Search these 3 files simultaneously and summarize each"
  • "Run tests and lint in parallel"
  • "Search for X in the codebase while reading Y"

Limits

Setting Default Description
Max parallel sub-agents 5 Prevents resource exhaustion
Sub-agent max turns 200 Per sub-agent conversation turn limit
Sub-agent max tokens 4096 Per sub-agent response token limit

Behavior

  • Sub-agents inherit the parent's approval posture — they do not auto-approve. A session that prompts you before Bash/Write/Edit keeps prompting inside any sub-agent it spawns (crates/wcore-agent/src/spawner.rs:2434). A parent already running with auto_approve on is the only case in which a child auto-approves.
  • Sub-agents do not save sessions
  • By default a sub-agent's streaming output is discarded and only the parent writes to stdout. When the host wires the relay (the JSON-stream protocol path), the child's events are forwarded to the parent sink as sub_agent_event frames instead of being dropped.
  • All results are merged and returned to the parent agent

Hook System

Event-driven hooks execute shell commands at specific points in the tool lifecycle, enabling auto-formatting, linting, auditing, and more.

Hook Types

Type Trigger Behavior
pre_tool_use Before tool execution Non-zero exit blocks the tool
post_tool_use After tool execution Non-blocking; errors are logged
stop When agent session ends Non-blocking

Configuration

# Auto-format Rust files after modification
[[hooks.post_tool_use]]
name = "rustfmt"
tool_match = ["Write", "Edit"]
file_match = ["*.rs"]
command = "rustfmt ${TOOL_INPUT_FILE_PATH}"

# Auto-format TypeScript files after modification
[[hooks.post_tool_use]]
name = "prettier"
tool_match = ["Write", "Edit"]
file_match = ["*.ts", "*.tsx"]
command = "npx prettier --write ${TOOL_INPUT_FILE_PATH}"

# Audit Bash commands
[[hooks.post_tool_use]]
name = "audit-log"
tool_match = ["Bash"]
command = "echo \"$(date): ${TOOL_INPUT_COMMAND}\" >> .wayland-core/audit.log"

# Run lint on session end
[[hooks.stop]]
name = "final-lint"
command = "cargo clippy --quiet 2>&1 | tail -5"

Environment Variables

Hook commands can reference these variables via ${VAR} syntax:

Variable Description
TOOL_NAME Tool name
TOOL_INPUT Full tool input JSON
TOOL_INPUT_FILE_PATH File path (if the tool has a file_path parameter)
TOOL_INPUT_COMMAND Command (if the tool has a command parameter)
TOOL_INPUT_PATTERN Search pattern (if the tool has a pattern parameter)
TOOL_OUTPUT Tool output (post_tool_use only)

Matching Rules

  • tool_match: glob patterns matching tool names; empty = match all
  • file_match: glob patterns matching file paths; empty = match all
  • Default timeout: 30 seconds, configurable via timeout_ms

Prompt Caching (Anthropic)

Prompt caching stores system prompts and tool definitions on Anthropic's servers, so subsequent requests only process the changed parts.

  • First request: full input token cost + 25% write premium
  • Subsequent requests: cached portion costs only 10%
  • Cache TTL: 5 minutes (auto-renewed on each hit)

Configuration

[providers.anthropic]
api_key = "sk-ant-xxx"
prompt_caching = true   # default true (Anthropic only)

Token Stats

With caching enabled, stats show cache data:

[turns: 3 | tokens: 100 in (5000 cached) / 200 out | cache: 5000 created, 5000 read]

OAuth Providers

Two providers authenticate via OAuth rather than a raw API key, with tokens managed entirely by the engine:

  • Sign in with ChatGPT — interactive subscription login. Run wayland-core auth login chatgpt (loopback PKCE flow; --device for a headless device-code flow, --import-codex to reuse an existing Codex CLI login). This is the only wired auth login verb.
  • Grok (xAI) — there is no auth login verb for Grok. Use it with --provider xai; the engine refreshes its OAuth tokens automatically. Grok CLI logins are importable: if ~/.grok/auth.json exists, the engine reads and keeps it fresh.

Token Storage & Security

  • Tokens are stored through the same credential ladder as API keys: the OS keyring first, then the encrypted vault (WAYLAND_VAULT_PASSPHRASE_FD / WAYLAND_VAULT_PASSPHRASE), and a refusal if neither is available. There is no cleartext rung — on a host with no keyring and a locked vault, auth login fails with the remedy rather than writing your refresh token to disk in the clear. backend = "plaintext" does not apply to OAuth tokens.
  • A token file written by a build before v0.12.26 (~/.wayland/oauth/{provider}.json, directory mode 0700 and file mode 0600 on Unix) still works. The first time the engine reads it, the token is written into the ladder, verified by reading it back, and only then is the cleartext file removed. If no secure tier is available the file is left alone and you stay signed in.
  • Migration is one-way, so the engine refuses rather than lying about it. Every verified store drops a non-secret marker at ~/.wayland/oauth/{provider}.stored. If a later run finds that marker but neither tier can produce the token — a locked vault, a keyring that is not running — the load fails with a message naming all three ways out, instead of reporting "not signed in" to a user who is signed in. /config shows the same state as ⚠ signed in — credential store locked.
  • On Windows a token set is larger than one Credential Manager entry can hold (CRED_MAX_CREDENTIAL_BLOB_SIZE is 2560 bytes, and values are stored as UTF-16). The keyring backend spans an oversized value across sibling entries under a manifest, so the cap is not a limit on what can be stored. Nothing changes for values that fit.
  • wayland-core auth logout chatgpt clears all of it — the ladder entry, any leftover file, and the marker. It works even when the credential store cannot be opened, which is what makes it a usable escape hatch from the refusal above.
  • PKCE (S256) and a CSRF state token are mandatory on the login flow; the callback compares state in constant time.
  • Refresh is engine-managed: concurrent refreshes coalesce into a single network round-trip (single-flight), and tokens renew automatically before expiry — no manual re-login.

VCR Recording & Replay

Record real API interactions and replay them in tests — no API key or network needed.

Usage

# Record mode
VCR_MODE=record VCR_CASSETTE=tests/cassettes/my_test.json \
  wayland-core -k sk-ant-xxx "Read Cargo.toml"

# Replay mode (in tests)
VCR_MODE=replay VCR_CASSETTE=tests/cassettes/my_test.json \
  wayland-core "Read Cargo.toml"

Features

  • Auto-sanitization: sensitive headers (api-key, auth, token) are replaced with [REDACTED] during recording
  • JSON-formatted cassette files, editable by hand
  • Supports recording/replay of SSE streaming responses

AGENTS.md Hierarchical Loading

AGENTS.md files provide project-specific instructions that are automatically injected into the system prompt. Files are discovered hierarchically and merged from remote to near:

  1. Global: <config_dir>/wayland-core/AGENTS.md — user-level instructions for all projects
  2. Project hierarchy: Walk up from cwd to the git root (or home directory), collecting every AGENTS.md found along the way

Files closer to the working directory appear later in the prompt and take precedence (via LLM recency bias). Each file is annotated with its absolute path for traceability.

@include Directive

AGENTS.md files can include other files using @ syntax:

  • @FILENAME or @./relative/path — relative to the AGENTS.md file's directory
  • @~/path — relative to home directory
  • @/absolute/path — absolute path

Paths inside fenced code blocks are ignored. Includes are recursive (up to depth 5) with circular reference detection. Non-existent files and non-text files are silently skipped.

Example

Given this structure:

my-workspace/
├── .git/
├── AGENTS.md          ← workspace rules
└── packages/
    └── server/
        └── AGENTS.md  ← server-specific rules

Running aion in packages/server/ produces a system prompt containing both files, workspace first, then server.


Memory System

Persistent, file-based memory that allows the agent to retain project-specific knowledge across sessions. Memory is automatically loaded into the system prompt at conversation start.

Memory Types

Type Purpose
user User's role, goals, preferences, knowledge
feedback Corrections and confirmations on work approach
project Ongoing work context not derivable from code/git
reference Pointers to external systems and resources

Storage

Memory files live in a per-project directory under the global config:

<config_dir>/wayland-core/projects/<sanitized-project-path>/memory/
├── MEMORY.md              # Index (auto-loaded into prompt, max 200 lines)
├── user_role.md
├── feedback_testing.md
└── project_auth_rewrite.md

Each memory file uses YAML frontmatter:

---
name: auth rewrite
description: Auth middleware rewrite driven by compliance
type: project
---

Auth middleware rewrite is driven by legal/compliance requirements.

Configuration

Memory is enabled by default with no configuration required. The memory directory is auto-resolved from the current working directory.

Override the base directory via environment variable:

export WCORE_MEMORY_DIR=/custom/path

The legacy AIONRS_MEMORY_DIR is still honored as a backward-compat alias — useful if you're migrating from an older aionrs configuration. When both are set, WCORE_MEMORY_DIR wins.

On Windows, WAYLAND_BASH_SHELL=powershell (Windows PowerShell 5.1) or WAYLAND_BASH_SHELL=pwsh (PowerShell 7+) switches the BashTool interpreter from the default cmd. It is a no-op on Unix. See docs/tools.md for details.

How It Works

  1. Agent starts → memory directory resolved from project path
  2. MEMORY.md index loaded into system prompt (truncated at 200 lines / 25 KB)
  3. Agent reads/writes memory files using standard Read/Write tools
  4. Agent maintains the MEMORY.md index as memories are added or removed

Plan Mode

A read-only exploration mode where the agent focuses on understanding the codebase and producing an implementation plan before making any changes.

How It Works

  1. Agent calls EnterPlanMode → tool access restricted to read-only (Read, Grep, Glob)
  2. Agent explores code, designs approach, writes a structured plan in its response
  3. Agent calls ExitPlanMode → full tool access restored, plan optionally saved to disk

Configuration

[plan]
enabled = true                    # Register Plan Mode tools (default: true)
plan_directory = ".wayland-core/plans"  # Where plan files are saved

Workflow Phases

When in plan mode, the agent follows a structured 4-phase process:

  1. Understand — Explore the codebase with read-only tools
  2. Design — Identify files to modify, code to reuse
  3. Write the plan — Compose a clear, actionable implementation plan
  4. Submit — Call ExitPlanMode to restore full tool access

Context Compression

A three-tier automatic compaction strategy that prevents context window overflow during long conversations.

Tiers

Tier Trigger Method LLM Call
Microcompact Tool result count exceeds threshold or time gap Clears old tool result content, keeping the N most recent No
Autocompact Input tokens approach context limit LLM summarizes the conversation Yes
Emergency Input tokens near absolute limit Blocks further API calls, asks user to start fresh No

How It Works

  • Microcompact runs automatically: replaces old Read/Bash/Grep/Glob/Write/Edit results with [Tool result cleared], keeping the 5 most recent results intact. Triggered by count (>10 compactable results) or time (>1 hour since last assistant message).

  • Autocompact triggers when input tokens reach effective_context_window - output_reserve - autocompact_buffer. The agent calls the LLM to produce a conversation summary, then replaces history with a compact boundary marker. A circuit breaker stops retrying after 3 consecutive failures.

  • Emergency is the last safety net at effective_context_window - emergency_buffer. Always active regardless of config. Blocks API calls and prompts the user to compact or start a new conversation.

  • The effective context window is, in order of precedence: your context_window setting if you set one; otherwise the active model's real window when wayland-core knows the model; otherwise 200,000. So a 1,000,000-token model autocompacts at 967,000 and hard-stops at 997,000, a 128k model at 95,000 / 125,000, and an unknown model keeps the 167,000 / 197,000 defaults. Setting context_window explicitly always wins — including when you set it below what the model allows.

Configuration

[compact]
enabled = true              # Enable compaction system (default: true)
context_window = 200000     # Context window in tokens. OMIT to let the active
                            # model's real window drive compaction; setting it
                            # pins the window and overrides the model's.
output_reserve = 20000      # Reserved for output generation
autocompact_buffer = 13000  # Buffer before autocompact triggers
emergency_buffer = 3000     # Buffer before emergency block
max_failures = 3            # Circuit breaker threshold
micro_keep_recent = 5       # Keep N most recent tool results

Smart auto-compaction (#280)

Smart auto-compaction is a proactive pre-gate that fires the existing autocompact path early — when the conversation reaches a configurable share of the currently-active model's context window (default ~65%), instead of waiting for the static autocompact_buffer threshold (~91% effective). On a fire it (1) writes a non-destructive handoff of the live conversation to long-term memory (a compaction_handoff Episode — nothing is lost even if the LLM summary later rewords prose), then (2) runs the normal autocompact and continues the turn seamlessly. It emits the same CompactOffload host event as ordinary compaction, with the reason smart_window_pressure so the host can label the "Compacted — kept X of Y" chip with smart provenance.

The trigger is Flux-aware: it is computed against the active model's real window (preferring the served-model window Flux signals back) and re-evaluated every turn, so it tracks model swaps automatically rather than using a fixed token count.

Default-OFF. Because it fires well below the static threshold, it runs the LLM summarizer more often (more cost/latency). Soak it before enabling.

[compact]
smart_enabled = false            # MASTER GATE — default off; enable after a soak
smart_trigger_fraction = 0.65    # Active-window share that arms a proactive compact
                                 #   (clamped to the 0.60–0.70 band at runtime)
smart_release_fraction = 0.50    # Hysteresis low-water: re-arm only after the
                                 #   fraction drops below this (forced < trigger-0.05)
smart_cooldown_turns = 2         # Minimum completed turns between two smart fires
smart_min_shrink_tokens = 2000   # Cannot-shrink latch: if a smart compact frees
                                 #   fewer tokens than this, smart compaction
                                 #   latches OFF for the rest of the session
smart_handoff_to_memory = true   # Write the non-destructive handoff Episode

smart_trigger_fraction is clamped into the 0.60–0.70 band at the use site, so an out-of-band value (e.g. 0.95) is silently corrected rather than disabling the trigger. Anti-thrash is enforced by three independent latches (hysteresis arm/release, cooldown, and the terminal cannot-shrink latch); all must clear before the trigger can fire again.


File State Cache

An LRU cache that tracks files the agent has recently accessed, enabling read deduplication and automatic cache updates on writes.

  • Read dedup: When the agent reads a file it has already seen (and the file hasn't changed), the cache provides the content without re-reading from disk.
  • Write/Edit auto-update: After Write or Edit operations, the cache is updated immediately with the new content.
  • Dual eviction: Entries are evicted when either the entry count limit or the total byte size limit is reached.

Configuration

[file_cache]
enabled = true                # Enable file state caching (default: true)
max_entries = 100             # Maximum cached files
max_size_bytes = 26214400     # Max total cache size (25 MB)

Output Compaction

Post-processes tool output to reduce token usage. Three levels from lightest to heaviest:

Level Transformations
off No transformation
safe (default) Strip ANSI escape codes, merge consecutive blank lines, collapse carriage-return progress bars
full Everything in safe, plus: fold repeated lines, compact JSON indentation

TOON Encoding

When enabled alongside full compaction, TOON (Token-Oriented Object Notation) encodes uniform JSON arrays as compact tables:

[2]{id,name,role}:
  1,Alice,admin
  2,Bob,user

This is equivalent to:

[{"id":1,"name":"Alice","role":"admin"},{"id":2,"name":"Bob","role":"user"}]

TOON instructions are injected into the system prompt so the LLM understands the format.

Configuration

[compact]
compaction = "safe"   # off | safe | full (default: safe)
toon = false          # Enable TOON encoding (default: false)

Runtime Control

In --json-stream mode, the compaction level can be changed at runtime via set_config:

{"type": "set_config", "compaction": "full"}

Native Bash Output Compaction

On top of the generic levels above, the engine compacts verbose Bash tool output per-command before it enters the model's transcript. It parses the output grammar of common dev commands and reconstructs a compact, signal-preserving form:

Command What is kept
cargo build/check/clippy/test/nextest each error[E…]/warning with its full code-frame block (--> location, code frame, = help), the could not compile verdict, and test result: + FAILED names + panic blocks. Drops Compiling … spam and passing ... ok lines.
git status / diff / log status grouped by change-type with counts + capped path lists; diff structure (@@ hunks, +/-); log collapsed to one line per commit (drops Author:/Date:).
pytest / jest / vitest / go test the pass/fail summary, failing test names, and each failure's full traceback/assertion block. Drops passing lines.
grep / rg / find total match count + unique-file count + the first paths.
anything else (fallback) a generic shape classifier keeps error/warn/fail lines, else a head+tail with an omission marker.

Properties:

  • Default on. Resolved from ProviderCompat::compact_bash().
  • Fail-open. A parser that can't confidently parse falls through to the classifier, then to the raw output — the error signal is never dropped, and a non-trivial compaction always appends the last raw lines as a guaranteed tail. Output below ~40 lines / 8 KB is passed through verbatim.
  • Human sees full output. Only the model's transcript copy is compacted; the host/terminal still receives the complete result via the live stream.
  • Telemetry. Per-call (raw_bytes, compacted_bytes) is recorded on the tool-call trace (compaction_bytes) and a wcore_agent::compaction debug line is emitted, feeding a savings ("gain") report.

Disable it per provider/profile in ProviderCompat:

[providers.<name>.compat]
compact_bash = false   # default: true

Skills lifecycle (W9)

The skills lifecycle is enabled by default. When enabled, three subsystems become available to the engine:

  • F10 autonomous skill creation. A PatternDetector over recent TurnTrace history flags repeated tool-call sequences (length ≥ 5, repeats ≥ 3, stable input-key shape). Matches are written via DraftWriter::stage as P4 procedures with status = Staged. Staged skills are visible to wcore skills audit and operator promotion paths but never auto-load into the model's context. When structured_traces is also on, a TraceEvent with payload { "kind": "skill_drafted", "name": ..., ... } is emitted on each stage.

  • F11 curator. Runs on on_session_end. Reads active P4 procedures, scores them as success_ratio · ln(1 + use_count), dedupes overlapping descriptions (Levenshtein ≤ 5 keeps the higher-scored entry), and archives entries with use_count ≥ 5 and success_ratio < 0.20. Pinned procedures are never touched.

  • PUM (P5 user model; design §4.16). A session-end inference pass derives four keys from TurnTrace history:

    • preferences.tool_order — top-5 tools by raw frequency (ties broken by first-seen turn index).
    • tool_habits.recent_top5 — recency-weighted (last turn × 3, previous × 2, all others × 1).
    • language.primary"en" stub in W9; future revisions sample user messages.
    • working_hours.local_tz_window — 24h window stub in W9; tightens once W6 adds wall-clock timestamps to TurnTrace.

    Writes go through MemoryAccessGate with AccessToken::System (P5 is system-only-write per W5 L4).

Enabling

The skills_lifecycle flag is default-on. An operator can disable it globally or per project:

[observability]
skills_lifecycle = false

The flag is also valid in the global config file under the same [observability] section. An explicit false in either source wins; an absent value keeps the smart default enabled. Disabling the lifecycle does not disable memory itself.

The converse does hold, and it is asymmetric on purpose: [memory] enabled = false switches this flag off too, whatever it is set to. Everything the lifecycle produces is a durable artifact derived from the user's session, so a memory opt-out is an opt-out of all of it — see memory.md.

Generated drafts are written once to the canonical skills directory with an auto_drafted manifest. They remain inspectable but hidden from model listings, routers, guessed-name execution, and cross-project resolution. Review metadata does not grant activation authority. --skills-promote is temporarily unavailable until F23 provides a governed one-procedure-to-one-artifact promotion transaction; --skills-archive remains available.

Status in this release

W9 lands the four module-level pipelines (PatternDetector, DraftWriter, Curator, UserModelInferencer) and an end-to-end acceptance test (wcore-skills/tests/w9_acceptance.rs) that drives them against an in-memory MemoryApi. The engine bootstrap exposes the operator gate (observability.skills_lifecycle) and a real MemoryApi handle. As of W9.1, PatternDetector + DraftWriter are invoked from AgentEngine's per-turn loop when the gate is on (see crates/wcore-agent/src/engine.rsdetector / writer construction inside the turn loop). Session-end invocation of Curator (skills curation) and UserModelInferencer (PUM) is still routed through host-side hooks rather than fired directly from fire_on_session_end.

F12 GEPA (the eval-driven mutation loop that evolves skill bodies once drafts have accumulated win/loss data) ships under W10B, with a real LlmParaphraseProvider for the Paraphrase mutator in production (Phase 3 PA); test runs continue to use the fixture-replay provider for strict determinism.

Anvil — the gated forge (Smart Loops)

Anvil is the iterate-until-verified engine for work with a real executable gate (tests, build, typecheck). It forks a builder sub-agent into an isolated git worktree, runs the gate sandboxed (network-denied) after every attempt, climbs by accept-only-strict-improvement steps, and ends with a machine-stamped receipt. Only a passing gate earns verified — a model's opinion of its own work never does.

Zero-config quickstart (Anvil is ON by default):

cd your-repo        # has a test suite (cargo / npm / go / pytest / just / make)
wayland-core forge "fix the failing auth tests"

The gate is auto-detected from the workspace (explicit config always wins), the baseline is probed in a scratch worktree, and the winning change lands on an anvil/cand-N branch for review — your tree is never modified.

In a session, the same engine is the Forge tool: say "make sure this is right — iterate until it provably passes" and the model routes the work to the forge and returns the receipt.

Configuration ([anvil] in config.toml):

Field Default Meaning
enabled true Kill-switch. Availability, not activity: the forge only runs when invoked AND a gate exists. A project config can disable Anvil but can never re-enable a globally kill-switched one (tighten-only).
gate [] (auto-detect) The gate argv, e.g. ["cargo", "test"]. Empty = detect from manifests; the sandbox probe adopts the first candidate that actually executes.
driver_provider / driver_model unset (auto) The builder ("driver") seat. Auto: a connected FluxRouter key routes builders through flux-auto; otherwise an in-family mid tier (e.g. Sonnet) drives while your session model plans.

The escalation valve. When consecutive attempts fail with the same fail-set, the climb buys exactly ONE read-only diagnostic turn from your session (frontier) model, feeds its guidance back to the cheap builder, and resumes. valve_fires on the receipt counts it. Pay for the unblocking, never the grinding.

Safety model. The forge is invocation-only and refuses without a gate; gates run sandboxed (network denied, minimized env); baseline probes and builders operate in disposable worktrees, never your tree; builders have edit tools but no shell — the gate does the executing; trampoline gates (npm test, make test, just test) content-pin their dispatch manifest, and a candidate that tampers with it fails a Safety-class gate-integrity check that can never be traded away or verified.

Receipts. Every climb ends in one anvil_receipt (see the JSON stream protocol doc): terminal state, stamp, checks, iterations, valve fires, cost (priced: false renders as "unpriced", never $0). Climbs that run out of wall budget stop themselves and report an honest timed_out — never a silent kill.

Anvil is the checkable-reward sibling of Crucible: work with a real gate goes to the forge; judgment work with no checkable reward goes to the council.

Browser tool family (W8c.1)

wcore-browser is a multi-backend browser tool family. The three backends share an ARIA-tree-first surface (BrowserOp::Navigate / Snapshot / Click / Type / NewTab / Download) so prompt budgets stay under control regardless of which provider actually drives the session:

  • Camoufox — primary. Stealth-fingerprinted Firefox; the default choice for any production session.
  • chromiumoxide — local-Chromium fallback when Camoufox is unavailable.
  • Browserbase — cloud-hosted browser sessions, opt-in via BrowserPolicy.allow_cloud.

BrowserPolicy is the network boundary: a deny-by-default origin allow-list that gates every Navigate. A blocked op fires a browser_policy_denied protocol event alongside the error tool_result so hosts render the block as a distinct notification.

BrowserSupervisor owns process lifecycle, orphan-reaping, and binary-management; per-platform binaries are downloaded on demand via BrowserBinaryManager. There is deliberately NO Evaluate op in v1 — arbitrary JS injection is too easy a security regression for the first wave.

The plugin shell is wayland-browser (registers a BrowserToolSpec through wcore-plugin-api; no direct wcore-browser dep, per audit F2). Capability advertised on the wire via capabilities.browser_suite whenever the plugin loads.

Computer use (W8c.2)

wcore-cua is the multi-platform computer-use tool family — synthesized mouse, keyboard, and screenshot ops across macOS, Linux X11, Linux Wayland, and Windows backends.

Background-mode invariant: every CUA op MUST be performable without stealing focus from the user's foreground app. CuaPolicy enforces this at the type level and additionally gates:

  • per-app first-touch approval (default ON);
  • forbidden-app and forbidden-keycombo lists;
  • optional screenshot redaction.

On restricted Linux Wayland compositors the host adapter refuses registration at boot rather than silently degrading — operators explicitly opt out by flipping register_tools = false in plugins.toml for wayland-cua.

The plugin shell is wayland-cua (registers a CuaToolSpec through wcore-plugin-api; no direct wcore-cua dep). Capability advertised via capabilities.computer_use.

Plugins (W2.5 / W8a / W8c)

Plugins are compiled-in Rust crates discovered through inventory::submit!. Every plugin declares a plugin.toml manifest that gates which register_* surface(s) it may touch (tools, hooks, agents, skills, rules, MCP server, providers).

Currently shipped:

Plugin Surface(s) Role
wayland-ollama register_providers Reference provider-only plugin — local Ollama inference
wayland-browser register_tools Packaging of wcore-browser via BrowserToolSpec mirror
wayland-cua register_tools Packaging of wcore-cua via CuaToolSpec mirror
wayland-ijfw every surface Anchor plugin — exercises tools, hooks, agents, skills, rules, and MCP server end-to-end

The wire-side capability flags (Capabilities.plugins, .browser_suite, .computer_use) flip when the matching plugin is present, so the host UI can adapt without out-of-band coordination. See docs/json-stream-protocol.md §§1.N+6 through 1.N+10 for the per-event variants.