The newest release. Every earlier one is in the full changelog.
bun test; ignored providers print once (#4717)8gent keys set, bare / opens help (#4704)packages/telegram-bot/daemon-client.ts and packages/daemon/telegram-bridge.ts resume the same session on both sockets instead of creating a new one (packages/daemon/gateway.ts).packages/daemon/telegram-reconnect.test.ts (against the real gateway) and packages/telegram-bot/daemon-client.test.ts.apps/proxy/src/server.ts passes the client request's abort signal through packages/providers/index.ts, so a stop button or closed app aborts the in-flight request on the Ollama and compatible-API paths and the next request is not queued behind an abandoned generation. One hosted provider with its own client does not honour the signal yet.finally; the model step stays bounded by EIGHT_TURN_TIMEOUT_MS.apps/proxy/src/cancel.test.ts and packages/providers/chat-signal.test.ts (fake upstreams on 127.0.0.1).packages/mcp/client.ts, packages/mcp/transport.ts). If that fails, the server is removed from the tool list and status with one plain error, instead of showing as connected while the model calls a dead tool.packages/mcp/dead-server.test.ts, which also check that no spawned server process outlives close or drop.createStreamDeanonymizer in packages/permissions/pii-anonymizer.ts buffers a partial placeholder such as [EMAIL_1] across streamed chunks and restores the real value; packages/eight/clients/deepseek.ts uses it. Latent today (no streaming caller yet, provider off by default); the first streaming caller is safe by default.packages/eight/clients/deepseek-stream.test.ts (every two-way split, no network).bridgeTools in packages/mcp/tool-bridge.ts now hands each MCP tool's input schema to the model as the server declared it (jsonSchema() from ai), so enums, array item types and type lists such as ["integer", "null"] are no longer dropped. A top-level anyOf / oneOf / allOf is flattened into one object by normalizeMcpInputSchema: branch properties are merged (first definition wins, any property name is kept), allOf keeps every branch's required fields, anyOf / oneOf keep only fields required in every branch. An either-or tool used to reach the server with an empty argument object; its argument now arrives. The server's schema object is not mutated.EIGHT_MCP_LEGACY_SCHEMA=1 restores the previous converter for one release. Tests in packages/mcp/tool-bridge.test.ts.toOpenAIMessages, toAnthropicMessages and toOllamaMessages in packages/providers/index.ts, and apps/proxy/src/openai.ts, carry earlier tool calls and their reply ids through the PII gate, so step two of a multi-step tool loop no longer fails with a provider 400 or loses its tool_result structure.packages/providers/__tests__/tool-history-wire.test.ts and apps/proxy/src/proxy.test.ts.filterChatCapable in apps/tui/src/lib/model-selection.ts asks Ollama's /api/show for capabilities (2 s timeout, cached) and hides models without completion, keeping the name filter as fallback. A filtered model is reported as unable to chat rather than not installed.detectBestLocalProvider) still ranks by name and corrects itself one tick later. Tests in apps/tui/src/lib/model-selection.test.ts (injected fetch).EIGHT_VERIFY_GATE=1, a turn that wrote files and ran no read or test afterwards gets one verify nudge before it finishes, on both the native path (packages/eight/agent.ts) and the text-tool path (packages/ai/text-tool-loop.ts). Logic in packages/eight/verify-gate.ts. Off by default; turning it on needs a benchmark:v2 off vs on comparison first.packages/eight/verify-gate.test.ts, packages/eight/verify-gate-agent.test.ts and packages/ai/text-tool-loop.test.ts.packages/permissions/output-filter.ts. With EIGHT_OUTPUT_FILTER=1 exactly, the output of web_fetch, web_search and mcp_call_tool is shown to a judge model on this machine before the main model reads it. The judge answers YES or NO and quotes any embedded instruction; the quoted text is cut out and replaced with [output-filter: removed an embedded instruction]. If the judge flags text it cannot locate, the output is kept with a notice line in front telling the model to treat instructions in it as data. Long output is judged in 12,000-character chunks. The idea is the detect-then-remove defence from the PromptArmor paper (arXiv 2507.15219); prompt and code are our own.ToolExecutor.execute (packages/eight/tools.ts), after the secret scrub and before the ArtifactStore, so the native and text-tool paths both get it and no secret reaches the judge. Any other flag value, including unset, "", "0", "true" and " 1", returns the text unchanged and calls no judge. Other tools, read_file included (already confined to the working directory), are never judged./api/chat endpoint at EIGHT_OUTPUT_FILTER_HOST, else OLLAMA_HOST, else http://127.0.0.1:11434, and must be loopback; any other host is refused. Model EIGHT_OUTPUT_FILTER_MODEL (default qwen3:32b, since the paper needed a model of about that size for near-zero error rates), timeout EIGHT_OUTPUT_FILTER_TIMEOUT_MS (default 30000). The request does not follow redirects; a redirect counts as a judge failure. It fails open: a judge that errors, times out or is refused leaves the output unchanged and logs a warning. When only some chunks fail, removals from the chunks that were judged are kept and a notice line says part of the output was not checked. At most 8 chunks (96,000 characters) are judged per result; the rest passes through behind the same notice. A judge quote shorter than 8 characters (whitespace collapsed) is never cut, and the line-level fallback only cuts a single line containing the quote or a run of whole consecutive lines equal to it; anything else is kept behind the flagged notice.packages/permissions/output-filter.test.ts (deterministic fake judge, plus a fake Ollama served on 127.0.0.1) and packages/eight/tools-output-filter.test.ts (the executor hook, flag off and on). The planted text is a benign marker line. False-alarm rate on our own tool traffic and added latency per call are not measured yet; they decide whether this goes further.EIGHT_RESUME_ON_BOOT=1, packages/daemon/session-journal.ts brings a session back on boot with its history from the last checkpoint, wired through packages/daemon/agent-pool.ts and packages/daemon/index.ts. The old checkpoint is kept until the restore succeeds. Meant for a single-user machine.packages/daemon/session-journal.test.ts.EIGHT_STREAM_IDLE_MS streams the local text-tool reply and aborts a step only after that many ms with no output, so a slow model that keeps writing is no longer cut off at 5 minutes; with it on the per-step ceiling defaults to 20 minutes unless EIGHT_TURN_TIMEOUT_MS is set. Unset, behaviour is unchanged. Documented in docs/VESSEL-ABILITIES.md; tests in packages/ai/stream-idle.test.ts.EIGHT_PROVIDERS_ALLOW set (for example ollama,8gent), failover in packages/providers/failover.ts can no longer fall through to a provider outside the list; it throws a typed error instead of a silent cloud hop. Malformed failover chains are discarded fail-closed.packages/providers/failover-allowlist.test.ts.AUTORESEARCH_GATE=1, benchmarks/autoresearch/mutation-gate.ts splits tasks into tuning and held-out sets, and a batch of new prompt rules is kept only if tuning improves and the held-out mean does not drop. Rules restored from earlier runs that came from held-out tasks are removed, and a failed rerun scores 0.benchmarks/autoresearch/loop-state.json with the pruned rule list. An answer-key rule for a tuning id can still be kept. Tests in benchmarks/autoresearch/mutation-gate.test.ts.benchmarks/runner.ts --resume continues an interrupted run from the last finished item instead of redoing it, records the seed (--seed), and labels resumed runs in reports. Per-item results are written by benchmarks/run-store.ts.benchmarks/run-store.test.ts, including an end-to-end subprocess run against a local stub.EIGHT_MCP_MODERN=1, the MCP client (packages/mcp/client.ts, packages/mcp/transport.ts) reaches modern-only servers over stdio and HTTP and falls back to the legacy handshake, and the 8gent MCP server (packages/mcp/server.ts) answers both eras. Default behaviour is unchanged.packages/mcp/era.test.ts and packages/mcp/server-era.test.ts; the flag-off tests pin current behaviour.[error] agent is busy, so in a burst of 20 messages 19 are dropped. With EIGHT_SESSION_QUEUE=1 (exactly 1), AgentPool.chat() in packages/daemon/agent-pool.ts puts it in a per-session first-in, first-out line instead: messages run one at a time, in the order they arrived. When a turn finishes, the session is handed straight to the next waiting message, so a new arrival cannot jump the line.EIGHT_SESSION_QUEUE_MAX caps how many messages may wait per session, not counting the one running. Default 32: unset, or a value that is not a non-negative integer (abc, -1, 2.5), means 32, and 0 means no waiting line at all (every message to a busy session is refused as full). A message arriving when the line is full gets [error] session queue full and an agent:error event with session queue full. When a session is destroyed, every waiting message is dropped and gets [error] session ended; none of them runs.[error] agent is busy string and the same agent:error event. The dispatch rate limiter (packages/daemon/dispatch.ts, a per-caller rate limit) is a different refusal and is not changed. Tests in packages/daemon/agent-pool-queue.test.ts: a burst of 20 with the flag off still gets 19 refusals; with it on, 0 refusals, arrival order kept, never more than one turn in flight; a full line errors; an invalid max means 32 and 0 means none; destroying the session drops the waiters. Not measured yet: no burst has been run against a live daemon on a real model.communicationStyle value action-first, choice 6 in the onboarding communication step (packages/self-autonomy/onboarding.ts), also accepted by name. When picked, USER_CONTEXT_SEGMENT in packages/eight/prompts/system-prompt.ts adds ACTION_FIRST_STYLE: open with the action or answer, numbered steps, lists capped at five, commands in code blocks, plain failure statements, minutes for estimates, no recap or sign-off, and one closing "Next:" line. It opens with a precedence line saying these rules win over the base prompt's joke COMPLETED summary (packages/eight/prompt.ts) and the greeting and completion phrases in the personality block (packages/eight/agent.ts); a required completion marker becomes one plain line just before "Next:". Written in our own words; nothing on screen names a condition.sarcastic. Tests in packages/eight/prompts/action-first-style.test.ts, with the before lines checked against the previous system-prompt.ts. Whether replies actually follow the shape (checker pass rate on replayed cases, style on versus off) is not measured yet.ollama, 8gent, lmstudio, llama-server) dropped the whole user-context block, so every style, the user's name, role and non-English language, and the board briefing never reached a local model. packages/eight/agent.ts now appends that block after the project instructions, so the cached prefix before it is unchanged (#3222). The board briefing part goes only to a model on this machine (the #3236 runsOnBox rule), judged on the endpoint the request actually uses: the session baseUrl when set, otherwise OLLAMA_BASE_URL / OLLAMA_HOST for ollama and LLAMA_SERVER_URL for llama-server. A local runtime pointed at another host gets the user context without the board briefing. When the session's runtime is local, the same check is also made per request: when a turn is rerouted to another local provider (a model-not-found reroute or the tool-capability switch) and that provider's endpoint is off this machine, that request is sent with the user context but without the board briefing and the user-global files. Test in packages/eight/reroute-off-box.test.ts. The #3236 check for the operator's user-global instruction files now uses the same endpoint, so they no longer reach an ollama or llama-server host named only by those env vars. Users with no completed onboarding and no name get the same prompt as before. Test in packages/eight/user-context-local-prompt.test.ts.concise, detailed, casual, formal and action-first. sarcastic, the default and the fallback for an unrecognised onboarding answer, has no guide line (its prompt line is just the name), so it gets no reminder; neither does no style. The reminder is resent and reprocessed by the model on every round of the tool loop: about 1 KB for action-first, one short line for the others. It reuses the same "Communication style" line (communicationStyleLine in packages/eight/prompts/system-prompt.ts) and is sent as a harness note, not a second system message: Ollama's qwen3.8 renderer takes one system turn and the raw path folds every system message into it, which would move the cached prefix (#3222). It is never stored in the history. Table officers get none. Test in packages/eight/style-reminder-local.test.ts.user.json is loaded, rejected by 8gent preferences set style (exit 1, with the allowed list), and not taken from a cloud preferences pull; communicationStyleLine and composeSoulPrompt also emit nothing for it, so no style line and no reminder. The user's language reaches the prompt only when it is a language code (en, pt-BR, zh-Hant). The board briefing path now resolves through resolveHome() like other home paths (#3240). New packages/self-autonomy/communication-style.ts holds the set and both checks. Tests in packages/self-autonomy/communication-style.test.ts and packages/eight/user-context-guards.test.ts. Whether it changes reply shape on a local model is not measured yet.packages/providers/effort-policy.ts maps the existing TaskCategory from packages/ai/task-router.ts to a requested thinking level: simple to low, code to medium, reasoning to high, plus a review kind outside TaskCategory mapped to high (the task router has no review or security label). creative, an unknown kind or no kind leaves the provider default. ChatRequest gains an optional taskKind, read only by the policy and never sent to a provider.ProviderManager.chat() in packages/providers/index.ts applies it at one point, before the existing thinking resolution, and only when EIGHT_EFFORT_POLICY is exactly 1 and the caller left thinking empty. An explicit thinking always wins, and the level still downgrades or drops through resolveThinkingLevel for the active provider (today only openai and anthropic accept a level). Any other flag value, including unset, "", "0", "true" and " 1", returns the caller's request object unchanged.chatAnthropic (packages/providers/index.ts): the thinking budgets are 1024 / 4096 / 12288 / 32768 tokens but max_tokens defaulted to 4096, and the API rejects budget_tokens at or above max_tokens, so any Anthropic call with thinking at low or above would fail with a 400. When thinking is set, a max_tokens cap (caller value, or the 4096 default) is raised to the budget plus 4096 only when it is at or below the thinking budget; a cap already above the budget is kept as is. Calls without thinking send the same max_tokens as before.8gent --cli --task-kind <simple|code|reasoning|review> (packages/eight/cli.ts) passes the kind straight to manager.chat(). The accepted kinds are derived from the policy table, and ChatRequest.taskKind is typed to the same set. Any other value exits 1 with an error before a model is called (repeated --task-kind: the last one wins); without the option the request is the same as before. --json output now also carries usage (prompt, completion and total tokens) and thinking (requested and applied level) when the provider returns them; both keys are absent otherwise, so the default output is unchanged. The quick-answer lane is untouched. Tests in packages/providers/effort-policy.test.ts (OpenAI, Anthropic and Ollama payloads, Anthropic max_tokens above the budget) and packages/eight/cli.test.ts. The A/B on real tasks (tokens and pass rate, flag on versus off) is not done; it decides whether this goes further.packages/cron/blueprints.ts holds three blueprints (morning-brief, pr-watch, weekly-review) with typed slots: a 24-hour HH:MM time, weekdays (mon..sun, weekly-review takes exactly one), a choice from a fixed list, and length-capped text. fillBlueprint(name, slots) refuses a bad value with a message naming the slot, and returns the same options object RoutineManager.create() already takes, so there is no second scheduler. createFromBlueprint(mgr, name, slots) is the only creation path and refuses unless EIGHT_BLUEPRINTS=1 exactly.<note> or <repo> tag with a line saying the tagged text is the user's value, not an instruction. It may not start with -, allows letters in any script, marks, digits, spaces and basic punctuation, and refuses control characters, invisible (default-ignorable) characters, runs of 4 or more combining marks, $, backtick, ;, |, &, < and >, so a value cannot close its fence. The pr-watch repo must be owner/name (owner of letters, digits and -, not starting with -; a name not made only of dots). Unknown names are echoed JSON-escaped in errors. Every built schedule is checked against a five-field digits-and-*,/ pattern before it is returned.8gent blueprint list | add <name> key=value..., in bin/8gent.ts. With EIGHT_BLUEPRINTS unset or anything other than 1 it prints that blueprints are off, exits non-zero and writes nothing. add saves a routine to ~/.8gent/routines.json and says so; it refuses a slot given twice, __proto__ as a slot name, and a bare word, echoing the input JSON-escaped. Nothing runs routines yet (the cron commands and the daemon use other schedulers, and nothing calls RoutineManager.tick()), so this trial only saves them; there is no run command.packages/cron/blueprints.test.ts (catalog, validator, and a text slot staying one argv element in RoutineManager's runner with spawn replaced) and packages/cron/blueprint-cli.test.ts (the CLI with a temp HOME: flag off writes nothing, flag on saves one routine, a bad slot exits non-zero, run is not a subcommand). No test starts an 8gent or a model.packages/providers/decision-readout.ts asks a user-run llama-server one typed yes/no question on /v1/systemone (llama.cpp PR 29818) and passes only when the returned probability is at or above a threshold (default 0.9). SeleneJudge.judge in packages/providers/local-judge.ts routes to it only when EIGHT_DECISION_JUDGE=1; with the flag unset, behaviour is unchanged. EIGHT_DECISION_JUDGE_URL (default http://127.0.0.1:8080) must be loopback, and EIGHT_DECISION_JUDGE_THRESHOLD sets the bar.SeleneJudge.isAvailable probes GET /health on the decision server instead of Ollama, so the kernel's local-first gate cannot silently route the trial to the cloud judge. JudgeVerdict gains an optional probability. A threshold outside 0.5 to 1 falls back to 0.9. 58 tests in decision-readout.test.ts run against a fake server on 127.0.0.1. Do not enable the flag where the kernel scorer or training loop runs until the replay passes; flag-on verdicts are still recorded as judgeSource "local", the same as Selene's. Accuracy on our questions is unmeasured; no model has been downloaded or run.benchmarks/autoresearch/speed-gate.ts runs a baseline and a candidate model config paired on the same fixed suite (JSONL of {"prompt"}, read with the canary traffic loader). It makes one untimed warm-up call per side, then calls the sides one at a time with the order alternated per input, and records each side's decision, probability and latency. It writes a JSON report and exits ACCEPT (0), REJECT (1) or NO WIN (2). ACCEPT needs p50 latency to improve by at least --min-improvement percent, every decision to match, and max probability drift within --max-drift. Any changed decision, drift over tolerance, a probability present on only one side (reported as maxDriftUnmeasurable: true), a failed, timed-out or null call, or a NaN or negative timing is REJECT. An improvement below --materiality or below the declared margin is NO WIN. All thresholds and --timeout-ms must be passed, and the suite must exist and hold prompts; otherwise it is a usage error (exit 4) and nothing runs.callModel: it posts to {url}/api/chat with format: "json", temperature: 0 and a fixed seed (--seed, default 8, written to the report), under a fixed system prompt asking for {"decision": <one short label>, "probability": <0..1>}. Replies are parsed defensively (code fences, whitespace and trailing punctuation are stripped; nothing in a reply is acted on). Suite prompts must name a fixed label set (for example "Answer yes or no."), or free-text decisions will differ and REJECT.url must be an http or https origin with no credentials, query, fragment or path; requests go to that origin plus /api/chat with redirects refused. Only loopback hosts (127.0.0.0/8, ::1, localhost) are allowed by default; any other host needs --allow-remote, which prints the host it allows. Link-local hosts (169.254.0.0/16, fe80::/10), the AWS IPv6 metadata range (fd00:ec2::/32) and any IPv6 host that embeds an IPv4 address (mapped, compatible, translated, or NAT64 64:ff9b::/96 and 64:ff9b:1::/48, for example [::ffff:169.254.169.254]) are always refused, unless the embedded address is 127/8. 6to4 (2002::/16) is not covered. Hostnames are not resolved, so under --allow-remote a name pointing at an internal address is not blocked; the stderr line says so. A reply body over 64 KiB, an empty, missing or non-string decision, or a decision over 64 characters is a failed call. The suite must be a regular file of at most 1 MiB and 1000 prompts with every line valid; anything else is a usage error. --out must not be a symlink, a directory or an input file, and the report is written 0600 through a temp file and a rename. When no input carries a probability, the report says driftChecked: false and warns. A crash or I/O error, including a failed report write after the verdict is printed, exits 5, never a verdict code.EIGHT_SPEED_GATE=1; otherwise it prints that it is off and exits 3 without calling anything. Run: EIGHT_SPEED_GATE=1 bun benchmarks/autoresearch/speed-gate.ts --baseline base.json --candidate cand.json --suite suite.jsonl --min-improvement 5 --materiality 0.6 --max-drift 0.05 --timeout-ms 60000 --out report.json, where each config is {"url","model","label"}./api/chat endpoints (Ollama and the 8gent provider) are served. Marlin (JSON-RPC over stdio) and moshi-mlx (websocket) need their own callers and are not covered. The probability is the model's self-report, not a scorer probability read from logits as in the study that suggested this gate. One timed sample per input; the report gives per-side sample counts and warns when the suite has fewer than 20 inputs. If the two models do not both fit in memory, alternating them measures load time, not inference; set Ollama keep_alive or check both are resident (ollama ps) before trusting a p50.benchmarks/autoresearch/speed-gate.test.ts.packages/mcp/transport.ts called getWriter() on Bun's spawn stdin, which is a FileSink, not a WritableStream, so every request threw and no stdio MCP server could ever connect. It now writes to the sink and flushes. Regression test: packages/mcp/transport.test.ts (fails on the old code, passes on the fix).PATH, HOME, USER, LOGNAME, SHELL, TERM, LANG, LC_* and TMPDIR from this process, plus the env its own config entry names; API keys and tokens in the parent environment no longer reach it (tested with a sentinel OPENROUTER_API_KEY). Output is split on newlines scanning each chunk once, and a single message over 16 MB (MAX_MESSAGE_CHARS) closes the transport, kills the server and fails every pending request (tested with a 40 MB flood). When the server's output ends, pending and later requests fail at once instead of waiting out the 30 s timeout, and a rejected flush() is caught.MCPClient.connect(), the stdio transport could not write (entry above), and the text-tool path (ToolExecutor: ollama, lmstudio, llama-server) never offered the MCP tools. With EIGHT_MCP_LEAN=1 (exactly 1), the ToolExecutor offers mcp_list_tools and mcp_call_tool, connects the configured servers on first use (never at startup; one retry if none came up), and serves them lean through packages/mcp/lean.ts. Unset, empty, 0, true, 1 and any other value leave the MCP tools as they were: same tool definitions, same mcp_list_tools and mcp_call_tool answers (tested). Not behind the flag, so they apply with it off too: the default policy rules on .8gent/mcp.json and .8gent/policies.yaml and the resolved_path matching for write_file and delete_file (below); mcp.json resolved under EIGHT_HOME through resolveHome(); a server that fails the handshake is closed (MCPClient.connect); the per-call MCP approval card in the TUI now shows the call and its arguments, or refuses (below); without the TUI, the per-call MCP terminal prompt shows the sanitised call and defaults to No ([y/N], a bare Enter declines); the AI SDK and REPL MCP listings replace control and bidi characters.mcp_list_tools {query} returns the 8 best word matches, one line each; {tool, server} returns that one tool's full input schema; no arguments lists servers and counts. mcp_call_tool {fields: ["totals.net"]} keeps only those dotted paths of a JSON answer (own keys only; __proto__, prototype and constructor refused; a missing path reports the top-level keys). An answer over 4000 characters is written to a 0600 file named by the harness (O_EXCL, O_NOFOLLOW) in a per-session 0700 dir under <data dir>/tool-results/, and the model gets the path plus a 400-character preview.\u0041 escape reaches the scanner only after projection decodes it (tested for \u- and \/-escaped secrets in a spilled projection; with today's scanner rules the \/ form is already caught by the first pass, the \u form only by the second). What the scrubber guarantees is what the scanner recognises: an escaped secret in an answer called without fields is written as it arrived, escaped, in the file and in the answer.mcp_list_tools or mcp_call_tool reads <home>/.8gent/mcp.json once and shows an approval card through the existing MCP approval path (askMcpStartApproval in packages/permissions/mcp-gate.ts) naming every server with its command and args (an arg holding a space or quote is quoted) and the NAMES of the env variables its config sets, or its URL without credentials or query; env values and headers never appear, and the working directory the servers start in is on the card. In the TUI the card shows every line in full, wrapped. The TUI decides the fit, not the gate: after Ink lays the card out (display width, so CJK and emoji count double), if the card's own box is not inside every box around it and the screen, it is taken down and the request refused ("does not fit on this screen"), nothing asked and nothing started. It re-checks at render (a resize included; the card's hook listens for resize itself) and again at key time, so a card pushed out of view by something growing above it, or hidden by a display: none box (a 0-sized card counts as not shown), is refused by the next key, never answered by it. The card takes a key only once it has been laid out whole at the current terminal size and no key has come for 500 ms: any key before that, chat text included, restarts the wait, and a card key in it is swallowed, so "okay" typed as the card appears does not approve it. Without the TUI, the terminal prompt shows the same sanitised text and defaults to No: bare Enter declines. While the flag is a trial, a server's config env may only set credential names: upper case, ending _TOKEN, _KEY, _SECRET, _PASSWORD or _ID. Any other name is refused before the card and nothing starts, because a variable can change what program runs or what code loads and the card would not show what really runs. This is an allowlist: the denylist of rounds 1 and 2 (PATH, NODE_OPTIONS, LD_* ...) kept missing names such as JAVA_TOOL_OPTIONS, HOME, PIP_INDEX_URL, SHELLOPTS with PS4, and NODE_TLS_REJECT_UNAUTHORIZED. Plain settings such as AWS_REGION or LOG_LEVEL are refused too, for now (tested: a PATH override, NODE_OPTIONS=--require, JAVA_TOOL_OPTIONS, HOME, and every name 8SO probed). Approved, exactly that list starts, with no re-read in between; denied, nothing starts and the answer stands for the session; every such refusal says to restart the session to be asked again. Infinite starts without a card; with no card and no terminal the answer is no. The card is separate from the per-call card (#3230), so approving one call to server A never starts B. Parallel first calls share one card and one start, a server that fails the handshake is closed rather than left running, and started servers are closed on process exit. Listing is ungated and allowed in Plan mode, so in Plan mode the lean listing does not start servers; mcp_call_tool is refused in Plan mode before anything starts (tested through the executor).mcp.json is resolved through resolveHome(), so EIGHT_HOME decides which servers can start. New default policies ask before an 8gent writes or deletes .8gent/mcp.json or .8gent/policies.yaml; in the text executor, as with the other require_approval rules on those actions, that means the call is refused. The write_file and delete_file rules match the path as typed and resolved_path, a new field the policy engine sets for those two actions: the path resolved against the executor's working directory, ~/ expanded, symlinks followed (resolvePolicyPath in packages/permissions/policy-engine.ts), so ./, //, a/../ and a relative mcp.json from inside ~/.8gent are caught too. A shell redirect's target is checked the same way, against the executor's working directory (the bash segment gate now passes it through evaluateCapabilities), so echo {} > mcp.json run from inside ~/.8gent asks. The run_command rule is a literal-string match only: it catches any command that spells either path out, including read-only ones such as cat ~/.8gent/mcp.json, not one that builds it (cd ~/.8gent && ... > mcp.json, "$HOME/.8gent/"mcp.json, mcp?json). It is a speed bump, not a boundary.askMcpApproval) used to show only its title, "MCP tool call". It now shows mcp_call_tool <server>/<tool> <arguments as JSON> on one line, control and bidi characters replaced, wrapped in full; a call the TUI cannot show whole in the chat column is refused with nothing sent, by the same check as the start card.packages/eight/tools-mcp-lean.test.ts), characters entering context. Today (flag off) the numbers are moot: nothing connects, so every MCP call returns "No MCP tools available" or "not connected". The comparison is therefore naive wiring (flag off, client connected by hand in the test) against lean wiring, with spill and chip paths left out of the counts because their length depends on where HOME is: listing 10,417 vs 208 (plus 299 for the one schema fetched); a 20 KB answer 20,050 vs 625, or 26 with fields; a 50 KB answer 1,115 (the existing ArtifactStore chip, #2463, already replaces results over 50,000 bytes) vs 625.packages/ai/tools.ts (cloud providers), the bridged per-tool ToolSet (client.getTools()) and the REPL /mcp-tools. None of them connects a server, so on their own they still reach no MCP tool; once the lean path has connected in a process they see its servers in full, untrimmed. The AI SDK mcp_list_tools and the REPL /mcp-tools now replace control and bidi characters in server names and descriptions and flatten descriptions to one line. Follow-ups: the spill dir has no disk budget and no sweep of earlier sessions' dirs (a 7-day sweep was cut from this change to keep it small), and spill dirs are keyed per process, not per 8gent session. The result store (storeResult) is tool-agnostic so #3477 can reuse it; until #3477 picks one, it and the older ArtifactStore (#2463: 0644 files under a pid-named dir) are two stores.apps/tui/src/lib/intro-gate.ts (readSeenVersion, markIntroSeen) and packages/voice/voice-resolver.ts (listInstalledSystemVoices) now default to resolveHome() from packages/core/home.ts instead of calling os.homedir(), so EIGHT_HOME redirects ~/.8gent/intro-seen and ~/.8gent/cache/system-voices.json like the other migrated paths. The voice cache path is resolved on each call rather than once at module load, so a home set after import is honoured. Behaviour is unchanged when EIGHT_HOME is not set.tests/security. No committed test drives the default-home path; it was checked by hand with EIGHT_HOME set after import. Coverage is indirect: intro-gate.test.ts and voice-resolver.test.ts exercise the same functions with an explicit home or cache path, packages/core/home.test.ts pins resolveHome() precedence, and the ratchet pins that neither file calls homedir().run_command calls the shell sanitizer had refused for && or ; chaining, which never ran. Both runs hit 51 calls and stopped one call before the commit, and the half-written last sentence came back as if it were the answer.mcp_call_tool, still counts as an executed call.packages/eight/breaker-refusals.test.ts.apps/tui/src/components/IntroBanner.tsx now makes 2 direct homedir() calls, not 3, since the intro redesign moved the seen-version reads into apps/tui/src/lib/intro-gate.ts. Its entry in tests/security/fixtures/homedir-baseline.ts drops from 3 to 2, which clears the stale-entry failure. The baseline only shrinks here; no entry is added and no assertion changes.homedir() (apps/tui/src/lib/intro-gate.ts, packages/decide/rules.ts, packages/permissions/owner-identity.ts, packages/voice/voice-resolver.ts, scripts/build-finalize.js, scripts/pack-smoke.ts) and bin/8gent.ts grew from 2 to 3. 8SO has reviewed all seven and ruled on each; the changes land in follow-up PRs under #3394. The two grader isolation failures reproduce only when bun is not on PATH; with it on PATH they pass. They also stay open under #3394 until CI runs the security suite with bun on PATH.8gent provider is the configured Ollama, but the TUI took its model list from the registry, which declares eight-1.0-q3:14b. That model is not installed on most machines, so a session on 8gent kept it, and every turn asked Ollama for it (a 404 in about 10 ms), then, because no failover chain is keyed on that id, sent the same id to OpenRouter, which does not serve it, and ended in "All providers exhausted". The TUI now lists what Ollama has installed for 8gent as it already did for ollama, so the existing model check replaces a missing model once, before any turn, and says so in one line: "eight-1.0-q3:14b is not installed in Ollama, so this session uses <model>. Pick another with /model." An explicit --model is still never replaced (#3084). Tests in apps/tui/src/lib/model-selection.test.ts. #3332 stays open for the registry default (packages/providers/index.ts), 8gent run --provider 8gent without --model (packages/eight/run.ts:246), packages/eight/index.ts, packages/daemon/goal-rpc.ts, packages/orchestration/role-config.ts:71, the failover chain entry at packages/providers/failover.ts:139, 8gent doctor reporting the resolved default, the CLAUDE.md provider line, and a test for EIGHT_MODEL.test/cli.test.ts using Bun's $ without importing it, ran bun test, got exit 1, staged and committed anyway. git_commit, and a git commit through run_command (also with leading NAME=value assignments, an env or command prefix, a full path to git, or git options before commit), now go through packages/eight/commit-gate.ts first. In a repo whose package.json has a test script, the suite runs (bun run test with a bun lockfile or a bun script, else pnpm test, yarn test or npm test) through the 8gent's own gated run_command. A red suite, including one killed by a signal, is returned as the tool result, starting [COMMIT BLOCKED] Not committed: with the first failing line and the end of the output, and nothing is committed. The marker counts as a failed call in the honesty ledger, so the reply cannot claim the commit..md or .mdx files, LICENSE, NOTICE): commit as before. A .txt file or code under docs/ still runs the suite. The changed and untracked files are hashed by path and content, so a tree unchanged since a green run this session commits without running the suite again; staging does not count as a change. A red result on an unchanged tree is reused, not rerun, and after two refusals on the same tree the commit goes through marked [COMMIT GATE] Committed with ... still failing, so a suite that was already red cannot trap the 8gent. The same rule also lets an 8gent that retries without changing anything commit a suite its own work turned red; that commit carries the same failing marker. A suite that does not finish in EIGHT_COMMIT_GATE_TIMEOUT_SEC (default 120, at most 300), a runner that cannot start (exit 126 or 127 with a "not found" or "cannot execute" line: no bun, yarn or pnpm on PATH, or a script calling a missing binary), a run a permission gate stops, output the gate does not recognise as a clean run does not block: the commit is made and says it is not verified. An exit 126 or 127 without such a line is red. A commit aimed anywhere (git -C, --work-tree, --git-dir, GIT_DIR=, GIT_WORK_TREE=) is checked against this repository's suite, exactly like a plain git commit; the gate does not try to work out which repository git will commit to. When the suite passes but tracked files have unstaged changes, the commit says that what it holds is not verified, since the suite ran on the working tree. Output is scrubbed for secrets before it is shortened. EIGHT_COMMIT_GATE=0 turns the gate off.ToolExecutor) are gated. That is the ollama, lmstudio (LM Studio) and llama-server providers, any provider with EIGHT_TEXT_TOOLS=1, and 8gents spawned with an edit scope. The default 8gent provider (local eight-1.0) and every API provider (OpenRouter, including the free failover tier, and the rest) use the native AI SDK git_commit and run_command in packages/ai/tools.ts, and are NOT gated yet; that is #3403. EIGHT_TEXT_TOOLS=0 also moves ollama and the other two off the gated path. Known costs, tracked in #3404: a suite that was already red costs one run and two refused turns per commit before it goes through, and a suite that times out is rerun (and waited on) at every commit. Tests in packages/eight/commit-gate.test.ts drive the real ToolExecutor in throwaway git repos, including the pilot's missing-$ test and a missing runner; they put the running bun first on PATH, so they pass the same with or without bun on the shell's PATH.D=$HOME; rm -rf "$D", rm -rf "${HOME:?}", rm -rf "${X:-$HOME}" and rm -rf "$DIR/"* (an unset $DIR leaves /*) only escalated. packages/decide/rules.ts now checks every text a path could expand to, as text only: a same-line NAME=value or export NAME=value gives its value, $HOME always stays $HOME, and any other variable may also be empty unless ${NAME:?} forbids it, so the safe idiom rm -rf "${DIR:?}"/* still escalates. A prefix assignment (D=/ rm -rf "$D") records nothing, as in sh. Bare, single-quoted and double-quoted forms all block.., *, ./* and .* as a recursive delete target block when the working directory is home or a system path, set by a cd earlier on the line (bare cd is home) or by the new optional cwd argument to decideRules. No caller passes cwd yet; wiring it from the System One gate is #3398. A project directory or an unknown working directory still escalates.D=/; true || D=build; rm -rf "$D" blocks). On a line with &&, || or if/while/case, a first assignment may not have run, so the unset value stays possible. More expansions than the guard can judge (over 64 candidates, nesting deeper than 8, or more than 64 $ in one argument) block instead of being cut short. Intended new blocks, each with an escalating rewrite: rm -rf "$TMPDIR"/* (TMPDIR is unset on most Linux and CI hosts; use "${TMPDIR:?}"/*), rm -rf "$PWD"/*, rm -rf "$OUT/bin" (use ${OUT:?}), and rm -rf * after a relative cd that follows cd ~. Known cost: D=$HOME; D=$D/code/app; rm -rf "$D" blocks; assign once instead.packages/decide/allowlist.ts) gave cat "$F", ls "${HOME}" and find "$X" -name a a pass without the judge, because it looked for $ only outside quotes. Any unescaped $ inside double quotes, other than $?, is now no-opinion. echo '$HOME', echo $? and echo "\$HOME" keep their verdicts. Tests in packages/decide/guard-bypass.test.ts.packages/settings/store.ts built its path from os.homedir(), which Bun fixes at process start, so the temp $HOME test preload did not redirect it and any test calling loadSettings or saveSettings read or wrote the real ~/.8gent/settings.json. The path now comes from resolveHome() (packages/core/home.ts, so EIGHT_HOME then $HOME), resolved on each call. With EIGHT_HOME unset and $HOME unchanged the path is the same as before. packages/settings/store-home.test.ts sets HOME to a temp dir after import and checks the path, a save and load round trip, and that EIGHT_HOME still wins. The file is removed from the homedir() ratchet baseline.packages/permissions/s1-rm-nothing.ts passes an absolute path without the judge only when it is absent (#3381, unchanged). An absolute path that exists always goes to the judge, on every platform; the birth-time allowance from #3395 is withdrawn. Tests in packages/permissions/s1-rm-nothing.test.ts.archive_removes_source (packages/decide/rules.ts) escalates archive commands that delete their inputs: zip with -m/--move (also inside a short-flag group), tar/gtar/bsdtar with --remove-files, and 7z/7za/7zr/7zz with -sdel. Plain archiving still passes.packages/permissions/s1-created-files.ts records a run_command redirect target as the session's own only if, when the command ends, the file at that path is still the one created for it before the command started. Each absent target whose parent resolves inside the working directory is created empty just before the command runs (at most 64 per command), so the target exists, empty, while the command runs. A recorded target that the command replaces is forgotten. A command that any rule escalates or blocks, or that changes directory, records nothing. Tests in packages/permissions/s1-created-files.test.ts and packages/decide/rules.test.ts.rm -f /tmp/todos.json blocked by the judge with no rule fired. The rm-of-nothing bypass in packages/permissions/s1-rm-nothing.ts refused every absolute path. It now passes an absolute path that is absent, has no .., . or empty segment, and whose parent directory exists and is either a real temp root (realpath of /tmp, or of os.tmpdir() when that is the macOS per-user /var/folders/../T) or reached from one only through real directories (not symlinks) owned by the current uid and not writable by group or others. Every component above the temp root must also be a real directory that group and others cannot write; the temp root itself must be unwritable by group and others or carry the sticky bit (/tmp is 1777); the only symlinks followed are macOS's root-owned /tmp and /var, matched by exact target. An absent intermediate directory, a symlinked one, one owned by another user, or one group or others can write, at any depth, goes to the judge, since it could be swapped for a symlink out of temp before the rm runs (8SO B1, B2, B3). The rules' text test (isTemp) is not used, so /tmp-x/..., /tmp/../etc/..., a temp symlink pointing out of temp, and a /scratchpad path outside every real temp root still go to the judge. With no rule fired, a relative path still goes to the judge. An existing file in a temp root always goes to the judge. Globs are still not covered (the other pilot block, rm -f todos-*.json). Same flag (EIGHT_S1_ALLOWLIST). Tests in packages/permissions/s1-rm-nothing.test.ts, including the 8SO negative cases.run_command now returns stderr on a successful exit too, labelled [stderr]. Output over 14 KB keeps the first 2 KB and the last 12 KB, and secrets are scrubbed before the cut.apfel / apple-foundationmodel, which only runs on macOS with Apple Silicon. On Linux (and Intel Macs), switching to the QA tab moved the session onto that model, and the first turn went to Ollama as apple-foundationmodel and had to fail over. resolveSpecForRole now returns no spec for an apfel or apple-foundation provider unless the host is darwin arm64, so the tab keeps the current provider and model. The provider is trimmed and lowercased before the check, so a hand-edited Apfel is gated too. apps/tui/src/hooks/usePerTabAgents.platform.test.ts covers linux x64, darwin x64, darwin arm64 and a settings override; it injects the host and the settings loader, so it never reads the machine's ~/.8gent/settings.json. Still open from the same issue: the apfel onboarding check, the local-engine count in the status bar, the apple-foundationmodel failover chain on non-Apple hosts, and the apfel model-list fetch in apps/tui/src/app.tsx (${baseUrl}/models), which has no timeout.read_file returned raw text, so a model asked which line something was on counted by hand and got it wrong (pilot l5-locate runs 2026-10-03_004939, _012538 and _064746 cited lines 18, 13 and 19 for an answer on line 16). Every line now starts with its number and a tab, as cat -n prints it, including the first-200-lines view of a long code file. New optional offset and limit arguments read part of a file and keep the file's real line numbers; a partial read ends with a note saying which offset to continue from. The tool description tells the model the prefix is not file content.edit_file still matches the text on disk. When oldText fails to match because the model pasted the number prefix in, the error now says so instead of a bare "could not find". When newText carries the prefix on every line, the edit is refused with an error naming it and nothing is written, unless the file already holds lines in that exact format. The prefix is never stripped silently, so the bytes written stay the bytes the policy gate checked.extractAutoMemories) strips the numbers before it parses package.json or reads a README, so project name, description, tech stack and purpose are still recorded. packages/eight/read-file-line-numbers.test.ts covers all of this through the real read_file path.packages/8dk (@8gent/8dk) gives a vessel a device of any kind. A device declares typed capabilities (sensors read, actuators act) with defineDevice(). Pairing needs the person's yes: they compare a 6-digit code shown on the device, see every capability, and choose which to grant; nothing is granted by default. The registry keeps only a SHA-256 of the device token, and a device whose manifest changes must pair again. DeviceToolAdapter turns each granted capability into an 8gent tool in the tools.ts shape (device__<device>__<capability>); ungranted ones are not listed and are denied by name. Every call goes through evaluatePolicy("device_use"), so YAML block and require_approval rules apply on top of the grant, and capabilities marked confirm ask on every call. Frames for the daemon WebSocket (device:hello, device:invoke, device:result and the rest) and a call correlator with timeouts are included. Revoking or unpairing takes effect on the next call. packages/8dk/__tests__/e2e.test.ts pairs a fake lamp, shows its tool denied before the grant, reaching the lamp and returning after it, and denied again after revocation. Not yet wired: the daemon /device route, BODY rail rows, saving pairings to ~/.8gent/devices/, and decision-audit entries. Concept studied from Meta's Muse Gadget SDK (Apache 2.0); no code copied.device_use is a policy action, and shadow (hedge) candidates are hard denied it, so a losing candidate can never touch a device./build.EIGHT_AGENT_DEPTH above 3 (by an 8gent already at the maximum) now exits with code 77 and an [AGENT DEPTH BLOCKED] message on stderr instead of starting. This covers the TUI, --cli, --rpc and every other command. --help, --version, outline, symbol, search and doctor still run, since they never start a model. The Agent constructor refuses too, as a backstop for every path that constructs an Agent, including entrypoints other than 8gent; the exit code there depends on the entrypoint (run currently exits 1, not 77, with the same [AGENT DEPTH BLOCKED] message). --cli never constructs an Agent, so it is covered only by the 8gent startup gate. A process at depth 3 still runs. run_command does not pass depth to its children yet; that follows in a later PR.packages/orchestration gains childAgentEnv() (the env a process an 8gent starts must carry: its depth, one deeper than the 8gent), processAgentDepth(), processAgentDepthRefusal(), AgentDepthError and AGENT_DEPTH_EXIT_CODE (77). Nothing calls them yet; run_command and the entrypoint refusal follow in later PRs. The test preload now clears EIGHT_AGENT_DEPTH, so a bun test started from an 8gent's shell runs at depth 0.8gent onboard --yes set the completion flag on a copy of the profile, so it never reached ~/.8gent/user.json: preferences get kept saying "Onboarded: no", status said onboarded: false, and the command still printed "Run with --yes". It now completes onboarding through the onboarding manager, which writes the flag and confidence score to disk. The interactive path is unchanged. packages/self-autonomy/onboard-yes.test.ts runs the real command in a throwaway home.~/.8gent/memory.db and ~/.8gent/config.json, which the memory, onboarding and settings stores do not write, so it reported "No memory DB yet" and "No config" on a set-up machine. It now checks ~/.8gent/memory/memory.db (or $EIGHT_DATA_DIR/memory/memory.db), ~/.8gent/user.json with its onboarding state, and ~/.8gent/settings.json. Doctor does not create the memory, profile or settings files. It does still write ~/.8gent/policy-checksum on first run through the NemoClaw check; that is a separate issue (#3344). packages/settings/doctor-paths.test.ts drives the CLI in a throwaway HOME (with EIGHT_HOME and EIGHT_DATA_DIR cleared) before and after onboard --yes and memory stats, and covers the EIGHT_DATA_DIR memory path.spawn_agent (both tool surfaces) and AgentPool.spawnAgent now refuse once an 8gent is at MAX_AGENT_DEPTH (3), before anything starts. Depth follows each async context and passes to child processes in EIGHT_AGENT_DEPTH. A malformed EIGHT_AGENT_DEPTH counts as the maximum, not as 0. The refusal reads [AGENT DEPTH BLOCKED], so the TUI tool trail shows it as blocked instead of "Started 8gent". Depth is not yet passed through run_command or WorktreePoolAgent (#3341, #3342).8gent daemon install wrote the systemd unit (and on macOS the launchd plist) under HOME or EIGHT_HOME, so with either pointing elsewhere systemctl --user enable failed with "Unit file com.8gent.daemon.service does not exist". The service definition now goes under the account's own home, the one the service manager searches and the daemon runs with.8gent-test-tmp-<pid>-* folder (TMPDIR, TEMP and TMP all point there, so Windows is covered too) and removes it afterwards. Folders left by an interrupted run are swept on the next run once their process is gone and they are 6 hours old. The 11 test files behind the biggest leaks make their temp folders with tempDir() from tests/temp-dirs.ts and clean them up, and the run fails if one of them leaks again. Other leftovers are listed in a [temp-dirs] line. tests/preload-temp-dirs.test.ts tests the guard and runs as part of bun run test./provider openrouter then /model auto:free silently switched to a paid OpenRouter model. auto:free now resolves to a live :free id and refuses any paid id; if no free model is reachable, the tab says why and retries. Failed lookups are not cached, and the model list request gives up after 10 s.git config --global change is picked up. Account-like names (admin, ubuntu, GitHub Actions) are not treated as a person's name.locate tool runs rg, and the ubuntu runner does not have it, which broke 16 locate tests. Without rg, locate still says so in its answer, and that path keeps its own tests. tools-open-on-write.test.ts now restores process.stdout.isTTY to its original descriptor. Before, it left behind a read-only copy on a non-TTY stdout, so auto-tune.test.ts failed 15 tests whenever it ran later in the same process.npm install -g @8gi-foundation/8gent-code no longer fails on a Linux box without Python or a compiler. node-pty and the tree-sitter packages are optional dependencies, so a failed native build is skipped instead of aborting the install. dist/pty-bridge.cjs now ships in the package, and without node-pty a terminal tab says how to install the build tools instead of crashing. CI installs every candidate package in a clean node:22-bookworm-slim container with install scripts on.web_fetch refuses loopback, private, link-local, CGNAT and cloud metadata addresses (IPv4 and IPv6, including mapped forms), pins each connection to the address it checked, and re-checks every redirect hop (at most 5). A host whose DNS answers mix public and private addresses is refused./work no longer matches /workspace inside bundled dependencies.rm -f of files the same 8gent created this session (with write_file, or a > / >> redirect in run_command), untracked by git and inside the workspace, no longer goes to the judge. The pilot's bun test > bunout.txt then rm -f bunout.txt now passes System One. Files it only modified, files another tab made, tracked files, rm -r and globs are judged as before. The record is in memory, per 8gent.EIGHT_SYSTEM_ONE=0. Unset, it asks only a calibrated judge (Selene today) and, when there is none or it cannot answer in time, checks with the safety rules and the read-only allowlist alone, saying so once. EIGHT_SYSTEM_ONE=1 (and Guarded) is strict: any judge found is asked; when none can answer, a block rule still blocks and everything else goes to the person on the normal card, never an allow (#3193). Headless, with no person, that stays a refusal. Every refusal now tells the 8gent not to run the same command again, which stops the retry loop.EIGHT_S1_SHARED_JUDGE) is on by default, so a machine loads Selene once in its local Ollama rather than once per tab; EIGHT_S1_SHARED_JUDGE=0 opts out. Before the first judge load, one line names the model, where it loads and its size.docs/guides/permission-modes.md.EIGHT_S1_SHARED_JUDGE=0 opts out). The shared judge lets every 8gent process on a machine use one copy of System One's model on the local Ollama server (EIGHT_DECIDE_OLLAMA_HOST, default http://localhost:11434) instead of loading one each. Same model, same verdicts. If the shared server stops answering mid-session, that session loads its own copy and carries on; it never falls back to another server or model. Off by default. Guide: docs/guides/system-one-shared-judge.md.NO_COLOR=1 now turns colour off everywhere, keeping bold, dim and inverse so the cursor stays visible. FORCE_COLOR still wins over it. The dark theme's danger red is brighter so it meets contrast guidelines. Guide: docs/guides/no-color.md.p into the chat box, hidden until the palette closed. The chat box now ignores Ctrl+letter as text.packages/decide) and the rest of the harness resolved OLLAMA_HOST differently: a bare host with no port (gpu-box, which the ollama CLI accepts) got :11434 for chat and no port (so port 80) for System One. Both now use one resolver, packages/local-model-server/ollama-host.ts.EIGHT_LOCAL_SERVER=llama-server runs 8gent Code on llama.cpp's llama-server with no Ollama at all: a llama-server provider on the text-tool path (LLAMA_SERVER_URL, default http://127.0.0.1:8080), and nothing in the process probes, lists or calls Ollama (onboarding, health, readiness, task routing, System One). Unset, today's Ollama behaviour is unchanged. Guide: docs/guides/llama-server.md.packages/local-model-server/llama-server.ts) passes the same contract suite as the Ollama adapter.~/.8gent/models/decide/*.gguf before the Ollama store, so a machine with no Ollama can still run it (or set EIGHT_DECIDE_GGUF).packages/local-model-server/): a LocalModelServer interface with capability flags and an Ollama adapter, so Ollama becomes one server among equals rather than the assumed default. Eight model-list and health call sites (/api/tags) now go through it with no behaviour change, proven by a snapshot of their requests and results taken before the move. A contract suite holds every adapter to the same rules.apps/tui/src/lib/figure-eight.ts).dispatch-policy.ts grants the telegram channel full capability on the strength of that, assuming the allowlisted chat is one operator's private chat. In a group that assumption fails: every member could prompt an 8gent holding tools in the operator's home directory. TELEGRAM_AUTHORIZED_USER_IDS is now checked at the same three points as the chat allowlist, and fails closed: without it, a private chat behaves as before and a group rejects every sender.agentBusy already admits one prompt at a time.TELEGRAM_OPERATOR_USER_IDS may decide. Conversation and consent are different powers.bun run lint had exited 1 on main since at least 2026-08-25, so the Validate job failed on every pull request whatever it contained. Ten errors across nine files are fixed: seven mechanical and behaviour-preserving, three where the rule mis-reads correct code and now carries a suppression with its reason. The 1417 warnings are untouched; this makes the gate work, it does not clean the repo./retro ships as a bundled skill, so the session retrospective is available out of the box: a short Socratic interview, a determinism table sorting friction into hook, command, advice or decision record, and a cap of one adopted change per retro.packages/table/message-speak.ts (new): a "play this message aloud" affordance for ANY Table message - synthesizes from the message's live content in real time via narrateTurn/voiceFor/HUMAN_VOICE, reused verbatim from huddle-voice.ts (no second TTS engine, no reimplementation of the Supertonic call). voiceForAuthor picks an 8gent author's own officer voice or HUMAN_VOICE for a human author - declared identity, never inferred, same rule huddle-voice.ts already documents. synthesizeMessageSpeech writes to a fresh temp directory removed in a finally regardless of outcome - nothing survives past the call, by design (this deliberately replaces the earlier persisted audio_url direction from #2875/#2876 as the general pattern; that stays as-is for the one message that already has a curated narration).packages/table/store.ts: new public TableStore.getMessage(id).packages/daemon/gateway.ts: wires POST /table/messages/<id>/speak in, mirroring the existing handleAuditAccess route idiom. Read authority reuses the exact listMessages({viewerId}) check channel:presence already exercises - a private channel human:local isn't a member of 403s, never leaks content.packages/table/__tests__/message-speak.test.ts: voice selection, ephemeral cleanup on success AND failure (injected fake narrator, fast), full HTTP status mapping (404/403/422/503/200), and one test that shells out to the REAL supertonic binary end to end (skipped, never failed, when it is not installed) proving genuine non-silent WAV bytes come back and no temp file survives.packages/table/schema.sql: messages gains nullable audio_url TEXT and audio_duration_ms INTEGER. Additive only - a message without narration is unaffected, and TableStore's constructor now runs an idempotent ALTER TABLE migration so an existing ~/.8gent/table/table.db (from before CREATE TABLE IF NOT EXISTS could pick up the new columns) gets them too, on every open, safely re-run.packages/table/types.ts: Message.audioUrl / Message.audioDurationMs.packages/table/message-audio.ts (new): the daemon-local narration path shape /table/audio/<messageId>/<file> and its loopback-only HTTP handler, mirroring huddle-stage.ts's handleStageHttp for a single persisted message instead of a live huddle turn.packages/table/store.ts: postMessage accepts optional audioUrl/audioDurationMs at creation; new attachAudio() narrates an already-posted message after the fact (author-only, same authority as editMessage).packages/daemon/table-routes.ts: message:post validates an optional audioUrl against the daemon's own served-path shape; new message:attachAudio frame for the after-the-fact case, broadcasting the existing message:updated event.packages/daemon/gateway.ts: wires handleTableAudioHttp in alongside the huddle stage's HTTP handler.packages/table/__tests__/store.test.ts covering postMessage with/without audio and attachAudio's author-only authority and round-trip through a re-read.This is the daemon half of Table message narration; the relay proxy
(8gent-glasses#pending) and Flow playback UI (8gent-flow#pending) land as
their own repos' changes.
benchmarks/gate.ts: the results TSV's model column now feeds a second aggregate, computeModelCategoryAverages, tracked in scores/ledger.json under a new models field (model -> category -> {avgScore, benchmarkCount}). A router change or an eight-1.0 checkpoint bump is now attributable to the model that produced it, separate from the blended category number. --update writes both breakdowns; bun run benchmark:gate prints a per-model attribution table by default (--no-by-model to hide it). Purely informational - a single model regressing never fails the gate, only a category's blended average does, unchanged from step 1.scores/ledger.json, scores/README.md, benchmarks/README.md updated for the new schema field and CLI flag.benchmarks/gate.test.ts covering per-model averaging, ledger merge/round-trip, comparison, and report formatting.benchmarks/gate.ts: compares fresh benchmark:v2 category averages against the checked-in scores/ledger.json baseline and hard-fails on any regression beyond a configurable noise band (default 3 points). New categories bootstrap instead of failing; --update seeds the ledger from a trusted run.scores/ledger.json: the public score ledger, starts empty - it is only ever written by real benchmark:v2 runs, never hand-typed..github/workflows/benchmark-gate.yml: runs the gate on every PR touching packages/eight, packages/providers, or packages/tools. Skips grading (never fabricates a result) when OPENROUTER_API_KEY isn't configured on the repo.benchmark:v2 script alias (benchmarks/autoresearch/harness-v2.ts) that README.md and AGENTS.md already documented but package.json was missing, plus benchmark:gate / benchmark:gate:update.pty-bridge.cjs now restores the execute bit on node-pty's prebuilt spawn-helper before the first spawn. bun install skips the package's lifecycle scripts, so the helper landed non-executable and every pty.spawn on macOS failed with "posix_spawnp failed." - the 5 PtySession test failures on any bun-installed tree.test.todo - pending work, never a fake pass and never a failure.isRemoteProvider no longer treats the hyphenated lm-studio as a remote provider.~/.8gent venv.done, and brings the panel forward for live tool activity, approval prompts, errors, and completion.Introduced @8gi-foundation/8gent-flow, a Mac-first relay that reuses
@8gent/eyes for local screen capture and exposes token-gated WebSocket frames
for iOS or browser clients on the same network.
--host 0.0.0.0 for iOS pairing.control.* messages for click, hover, scroll, type, and keypress control from paired iOS clients.--no-token relays unless explicitly allowed.Org-level Actions quota was hit (3,000 min/month). Workflow changes to bring usage well under cap:
swift job moved out of ci.yml into new swift-nightly.yml (nightly cron + manual dispatch + PRs that touch apps/8gent-computer/**). macOS runners bill at 10x; running it on every PR was the dominant cost.lint.yml deleted - bun run lint is already part of ci.yml.semgrep.yml and gitleaks.yml no longer trigger on push to main (PR + weekly cron + manual is sufficient).concurrency: cancel-in-progress added to ci.yml, semgrep.yml, gitleaks.yml, and 8gent-review.yml so superseded runs no longer complete needlessly.Out-of-process clients (8gent-computer, future TUI vessels) build their own workspaceIds and call fs.list / fs.read / fs.write. The daemon previously rejected unknown ids with fs: unknown workspaceId and had no wire method to register one - _registerWorkspace was in-process only. New fs.register({ workspaceId, root }) lets clients register a workspace before file ops:
workspaceId against /^[A-Za-z0-9_.-]{1,128}$/ (KV-key injection safe).default (which still resolves via EIGHT_WORKSPACE_ROOT / process.cwd()).root to exist and be a directory; refuses the filesystem root.Paired with 8gent-computer's ensureWorkspace calling this method on first sight of each workspaceId, this clears the "unknown workspaceId" failure that blocked every file op in fresh chats.
Planner, engineer, and judge roles are each assigned to a distinct local model, matched to that model's strength. Dynamic local-model detection probes what is actually installed on the host and assigns roles accordingly, so the pipeline adapts to the machine instead of assuming a fixed model set.
Runs the adaptive three-model pipeline end-to-end from the TUI.
The eyes package gains a Marlin caption + Whisper transcript sidecar. A new extract_video tool chunks long videos and merges the results; the knowledge-graph video-extractor folds them into the graph with extended EntityType / RelationshipType coverage.
The daemon /store route now exposes the project-scoped KnowledgeGraph over graph.* JSON-RPC, alongside the existing session.* / kg.* / fs.* surfaces.
New skill that narrates work aloud via local TTS at each milestone.
The TUI gains a living plan rail, and the system prompt enforces task discipline so multi-step work tracks against an explicit plan.
GoalClient was never wired into CommandInput, so /goal did nothing. Wired it through, added a version header to the TUI, restored the missing lmstudio case in runtimeForProvider, and fixed the LiveFocalStrip vs LiveFocalStripWithGoal swap that suppressed goal output.
Repaired loop observability in the orchestration package and set a saner fixer-iteration budget.
AGENTS.md, 8GENT.md, and CLAUDE.md previously held three different documents that drifted apart - different harnesses read different files and got different instructions.
AGENTS.md, the vendor-neutral open standard. 8GENT.md and CLAUDE.md are now symlinks to AGENTS.md, so any 8gent harness reads identical instructions regardless of which filename it looks for. Edit AGENTS.md only.AGENTS.md is auto-propagated from 8gi-governance. If that sync overwrites this file, ecosystem-section edits must be made in 8gi-governance, not here.The Validate job's grep-based secrets scan flagged HMAC_SECRET = "test-secret-for-local-mode-suite-only" in a daemon test file. It is an obvious fake fixture, not a credential. The scan now excludes __tests__ directories and *.test.ts files; gitleaks (the Secret Detection job) still scans those with proper allowlisting.
The instruction loader (packages/eight/instruction-loader.ts) searched 8GENT.md, AGENTS.md, CLAUDE.md in that order with first-match-wins per directory. In any repo carrying both 8GENT.md and AGENTS.md (including this one), 8GENT.md shadowed AGENTS.md, so the vendor-neutral open-standard file was never loaded by the 8gent.
AGENTS.md > 8GENT.md > CLAUDE.md. The vendor-neutral open standard wins; 8gent is not married to any vendor. CLAUDE.md stays a last-resort fallback that loads only when no vendor-neutral file is present, minimizing vendor exposure. Added instruction-loader.test.ts locking the priority order and nested-directory merge behavior.Two pre-existing test failures broke the Validate CI job on every Linux run, unrelated to feature code.
afterAll(cleanup) hook was registered unconditionally, and cleanup() constructs a KeychainVault, whose constructor throws on any non-Darwin platform. The describe block was already skipIf-guarded but the hook was not. The hook is now registered only on macOS.1000ms on CI, 200ms locally) so it still trips on a gross multi-x regression without flaking.The npm package shipped with five blocking bugs on Windows that left users stuck right after install. All five fixed.
bin/8gent.ts referenced __dirname/../apps/... / ../scripts/... / ../packages/... for the TUI, benchmark, demo, and pet commands. None of those source paths exist in the npm tarball (only dist/cli.js, bin/8gent-run.js, and a handful of assets ship), so spawned children silently failed on a clean install. Replaced with a BIN_DIR derived from import.meta.url, added repoRoot() detection for dev-only commands (clean error if absent), and bundled the TUI as dist/tui.js shipped via the files whitelist. The TUI command now prefers the bundled entry and only falls back to source when running from a checkout.readKeysFile did not strip the UTF-8 BOM. Notepad saves .env files with a BOM by default, so the first key parsed as OPENAI_API_KEY and every downstream provider lookup missed. Strip the BOM before parsing.createModel() and getRetryConfig() in packages/ai/providers.ts called .includes(":free") without checking that model is a string. CLI flag parsing can pass undefined when no --model is given. createModel now throws a clear error naming the provider; getRetryConfig guards the membership test.defaultRoleConfig() hardcoded { provider: "ollama", model: "qwen3:14b" } for any non-Darwin system, and ModelFailover.resolve() used the same as its hail-mary. Both now default to OpenRouter auto:free so a fresh Windows / Linux install boots into a working state instead of an ECONNREFUSED 11434. The TUI loadProviderSettings no longer pins "ollama" when providers.json is absent - it leaves the slot empty so detectBestLocalProvider() runs.defaultModelFor() in packages/eight/run.ts returned qwen3:14b for any unknown provider. runRunCommand() now probes ollama once and only routes to it when the daemon answers; otherwise falls through to openrouter / auto:free. Also fixed packages/eight/index.ts env var typo (EIGHGENT_MODEL -> EIGHT_MODEL) and the bogus glm-4.7-flash:latest default model.The TUI center panel is now scrollable. Pinned to the bottom by default; scroll up to read earlier turns without being yanked back when new output streams in.
useMouseScroll hook enables xterm SGR mouse mode (?1000h ?1006h) and patches both stdin.emit('data') and stdin.read() so wheel bytes are consumed before Ink's input parser sees them (Ink reads via 'readable' + stdin.read(), not 'data'). Mouse mode is torn down on four exit paths so the terminal is never left in capture mode.shift+↑/↓ scrolls one message, shift+PgUp/PgDn scrolls five. Plain arrows stay owned by the input field.Tradeoffs: drag-to-select needs Option (macOS) / Shift (Linux) while focused; tmux users need set -g mouse on.
New WebSocket route at ws://localhost:18789/store exposing session.*, kg.*, and fs.* over JSON-RPC 2.0. Lets the 8gent-code TUI and 8gent-computer Electron share sessions, knowledge-graph chunks, and workspace files against one daemon. kg.* chunks files into MemoryStore behind filename + content secret gates. fs.exec is deny-by-default with a small allowlist plus hard-deny patterns (download-pipe-execute, base64 smuggling, root-tree destructive commands). Capability-token auth (~/.8gent/server.token, mode 0600, constant-time compare); audit logs to ~/.8gent/audit/. 29 daemon tests pass.
New BODY section in the ActivityRail shows eyes/hands/handeyes state: ○ disabled, ● idle, ◉ in-flight. /eyes, /hands, /handeyes slash commands toggle per-session enabled state. Capability detection at launch auto-enables based on cliclick (hands) and the bundled AX bridge (eyes); handeyes needs both. New useBodyParts hook, 6 new ActivityRail tests. Also corrected bin/8gent.ts VERSION drift (0.16.0 → 0.17.0).
Pre-rendered KittenTTS (Jasper voice) narration plays over the launch music, synced to the splash typewriter cascade. Bundled as apps/tui/sounds/splash-narration.mp3 (218KB) so the TUI startup path stays dependency-free. Splash schedule extended to 13.5s; music volume lowered so the narration reads clearly. Killed on exit / SIGINT / SIGTERM / /quiet.
Ported 8 governance tables (submissions, agent_mail, agentTranscripts/Sessions/Context, shareLinks/Viewers/Events) and 6 function files into packages/db/convex/ so the unified 8GI Convex serves all 8GI surfaces, not just 8gent-code telemetry. 22 new indexes, no table collisions. Phase 1 of the Convex consolidation; unblocks the dashboard fold-in (#2550).
The launch splash now leads with the IGI pitch ("Your intelligence shouldn't be a subscription." / "Take back custody of your cognition." / "Infinite General Intelligence. free. local. open.") instead of the older Infinite-Gentleman framing. Same cascade timing and typewriter speed. The Infinite Gentleman line stays a sub-brand elsewhere (decks, social bios).
The standalone admin dashboard was folded into 8gi-governance's /internal/platform/* during the 2026-05-10 Convex consolidation and ran side-by-side for a day with no regressions. Deletes the entire app and drops one transitive dependency from bun.lock. Two historical doc references remain (descriptive, not build deps).
Settings, Notes, Ideas, BTW, Questions, Kanban, Music, Projects, and Terminal tabs were invisible whenever the 8gent was in any non-chat viewMode (PLANNING, RESEARCHING, etc.). The utility-tab switch in renderMainContent was gated on viewMode === "chat". Utility tabs are workspace-scoped, not mode-scoped; dropped the guard.
The 8gent now sees and selectively coordinates eyes+hands when stuck. Three body-parts in the public spine: hands (motor), eyes (perception), handeyes (sensorimotor coordination).
Eyes (perception) - shipped across 7 PRs (#2497 + #2500 + #2502 + #2511 + #2513 + #2524 + #2528 + #2532 + #2535):
~/.8gent/bin/8gent-ax-bridge - drops the Homebrew peekaboo dependency. Conceptual ancestry: Peekaboo (MIT, Peter Steinberger 2025); full attribution at packages/eyes/native/NOTICE. Built via bash packages/eyes/native/build.sh or bun install postinstall.resolveProviderId then tier-check then describe). Closes #2508 privacy bug. Routes via Ollama (local) or OpenRouter (remote).eyes_see, eyes_find, eyes_describe, eyes_wait_for. New perception category in tool-registry. Singleton Eyes instance per process.apps/8gent-eyes/ with 7 subcommands + --intent natural-language routing. AgentCLIDesign-compliant (--json default, deterministic exit codes 0/1/2/3/64, no telemetry beyond audit).thresholdDelta rename with deprecated thresholdPx alias; build.sh kebab-case fix.Handeyes (sensorimotor coordination) - shipped across 2 PRs (#2531 + #2536):
handeyes_locate_and_click, handeyes_click_and_verify, handeyes_type_and_confirm, handeyes_engage_struggle_mode, handeyes_exit_struggle_mode. New coordination category in tool-registry.DoomLoopDetector (#2534, RFC #2527 Option A):
'stuck' with { period, reps, windowSize, detectedAt, signatures } payload when a cycle is detected. Synchronous check(): boolean API preserved unchanged. Typed on/off/once overloads.Tests across this release: 32 (eyes) + 9 (eyes-cli) + 39 (handeyes) + 20 (doomloop) + 47 (eyes-polish) = 147+ across the body-parts work, all green.
Trust primitives:
AAA, ABAB, ABCABC, ABCDABCD patterns. 13 tests. packages/eight/tool-loop-detector.ts.packages/eight/tools.ts post-execution. Format: [REDACTED:<rule-id>].packages/permissions/path-guard.ts.packages/tools/bash-parser.ts. Runtime spawn-site wiring in follow-up #2484.Context engineering:
resultCache + isReadOnlyTool() helpers from tool-registry.ts. 13 tests.~/.8gent/artifacts/{sessionId}/{hash}.txt; model gets [ARTIFACT a3f9 132KB] reference + 1KB preview. PathGuard write-path symlink protection. 19 tests.8GENT_TWO_STAGE_COMPACT. 8 tests + 107 package suite green.Model-agnostic routing:
Audit:
~/.8gent/turns/{sessionId}/{turnIndex}.json. System prompt hashed (sha256, not full text). Tool result previews capped at 1KB; full content lives in ArtifactStore. 13 tests.packages/orchestration/subagent.ts uses ad-hoc SubAgentConfig per spawn, not Anthropic-style named profile literals - there's nothing scattered to consolidate.docs/boardroom-minutes/{date}-{slug}.md BEFORE Wave 1 dispatch.~/.claude/skills/CleanRoomPort/SKILL.md. AGPL-safe extraction discipline: no source copy, test-first, branch-from-origin-main, no TUI touches, no co-author trailers, sub-300 LOC per port, mandatory PR credit line.docs/boardroom-minutes/2026-05-09-openmonoagent-extraction.md and cross-posted to 8gi-governance for cross-repo audit.biome.json to flag noExplicitAny, useImportType, noUnusedVariables, useTemplate, useArrowFunction, useOptionalChain, noControlCharactersInRegex, noDelete, useExponentiationOperator, useNumberNamespace, noUnusedTemplateLiteral, noUselessTernary as warnings; promoted useConst to error. Existing violations stay visible as warnings (1100+) so the gate is unblocked while creating sustained pressure to fix them. New code with these issues will surface in PR review.lint into separate format / format:check / check scripts so the lint gate is about code rules, not whitespace. New scripts: lint:fix, format, format:check, check, check:fix..pre-commit-config.yaml runs biome check --staged on staged TS/TSX/JS/JSX/JSON files. Catches new errors at commit time without blocking on legacy warnings in untouched files.main and all PRs, with concurrency cancellation. format:check runs as advisory (non-blocking) until the large existing format diff is auditioned in a follow-up PR.biome.json - those directories hold third-party skill artifacts and quarantined code, not first-party source.tsconfig.json is already strict: true. noUncheckedIndexedAccess and exactOptionalPropertyTypes not enabled in this PR - both produce hundreds of errors across the existing codebase. Tracked for incremental adoption.DjDeck - premium audio deck (3-row LCD: track/elapsed, artist/duration, waveform/vol; reels inside the LCD; Ctrl+P/N/B/M/↑↓ transport; /dj close|open toggle). - AgentInstrumentStrip - bordered status cards with stacked uppercase label / brighter value (MODEL · AGENTS · TOKENS · BRANCH · AGENT · MIC · APPROVAL · SESSION). Middle-truncation on long model names. - ModeFooter - bordered cyan-active mode chips. Ctrl+Y cycles modes. - HeaderBar - calm cyan brand pill (per-letter 8gent colors) replacing the rainbow Header. - BottomBar - wraps the new stack so app.tsx holds one render call. - theme.ts + typography.tsx design-token foundation. Transient DJ feedback (Now playing / Stopped / Paused) suppressed at the chat call site.term_* 8gent tools (#2082).Ctrl+H fancy-header toggle removed (rainbow header retired).Ctrl+T is now unambiguously "new tab" (previously also displayed alongside the mode hint).Ctrl+Y is the new mode-cycle hotkey (most single-letter Ctrl combos collide with TTY control codes).^T:new ^W:close rendered once in HeaderBar (was duplicated by TabBar).HudMusicPlayer, EnhancedStatusBar, StatusBar, DetailedStatusBar, ShortcutDock no longer rendered (source files left intact in case any external surface still imports them).AGENT_MODES constant, compactAgentModeBar, TUI_AGENT_MODE_COMPACT_BELOW, fancyHeader state.$PATH, /spawn runs the preset's install recipe (npm/pip), waits for it, re-checks, and only then opens the chat tab. Long output from the installer is suppressed to keep the chat clean. Force-reinstall via /spawn <id> install. (apps/tui/src/lib/external-agent-runner.ts, apps/tui/src/app.tsx)npm install -g @8gi-foundation/8gent-code@latest --force. The --force flag fixes the recurring EEXIST error when stale bin/8 / bin/8gent / bin/8gent-code symlinks block re-install on npm 9+. Streams npm output, prints a restart hint when done. (bin/8gent.ts)8gent perms). Diagnoses macOS Accessibility + Screen Recording grants for the host terminal. macOS attaches privacy permissions to the parent process (Terminal/iTerm/Warp/etc), not to bun, so the hands tools silently no-op when the terminal isn't trusted. The probe runs a tiny osascript AX query and a small screencapture (checks output bytes - denied permission writes a sub-2KB placeholder) to detect what's missing. With --open, also launches the relevant System Settings → Privacy panes. (packages/hands/index.ts, bin/8gent.ts)Unreleased. The /spawn auto-installer landed on main between this publish and the next; it ships in the following release./quiet is run. afplay has no fade or seek support, so we delegate the fade to ffplay (ships with ffmpeg) using its afade=t=out filter - kills afplay, spawns ffplay starting at the same elapsed time with volume=0.15,afade=t=out:st=0:d=2.4, audio curves to silence over 2.4s. Falls back to abrupt cut if ffplay isn't on $PATH. (apps/tui/src/components/IntroBanner.tsx)apps/tui/sounds/launch.mp3 (auto-copied to ~/.8gent/sounds/launch.mp3 on first run; ui.introSound setting overrides). Volume 15% via afplay -v 0.15 so the music sits under the splash text and any future narration.dj.status() every 700ms; hidden when nothing's playing. Hotkeys (all Ctrl+Shift+ to avoid clashing with chat input): ⌃⇧P play/pause, ⌃⇧B prev, ⌃⇧N next, ⌃⇧↑↓ volume, ⌃⇧M mute. (apps/tui/src/components/HudMusicPlayer.tsx)packages/music/dj.ts)mute, shut, silence) - kills any in-flight intro music, prints a system message confirming the kill. Splash dismiss also calls it automatically.voice.outputEnabled setting. Each 8gent's text reply is spoken through macOS say when the user is on darwin. Toggle live with /voice on / /voice off.voice.perAgent settings map. Defaults: Orchestrator → Daniel, Engineer → Karen, QA → Moira. Falls back to voice.ttsVoice when a role is missing. Editable from /settings → Voice. Helper: getVoiceForRole(role, settings?) exported from @8gent/settings.{ detached: true } + proc.unref() so afplay survived Ctrl+C, kill, and crashed sessions - only pkill afplay could stop it. Module-level child handle is tracked; SIGINT / SIGTERM / exit / uncaughtException hooks kill it; detached flag is gone.apps/tui/src/hooks/useWorkspaceTabs.ts).apps/tui/src/components/message-list.tsx).event.text IS the final assistant reply; we no longer also push it as a greyish system bubble in front of the cyan assistant bubble (apps/tui/src/app.tsx).start() (training itself was already opt-in via ~/.8gent/config.json) - Heartbeat 8gents (git monitoring, self-heal, memory sync background loops)This aligns with Principle 2 ("free and local by default") and Principle 8 ("the work speaks for itself") - boot lean, layer features in only when the user asks for them.
If you were relying on background sync, kernel training collection, or heartbeat self-heal:
```bash
export 8GENT_FULL=1 # or add to your shell rc
```
Otherwise no action needed - your sessions, 8gent, tools, and /spawn all keep working exactly as before.
Measured 8gent ctor cost on this host (qwen3.6:27b config):
- Normal: ~4.5s (3 trials: 4864/4444/4532ms)
- LITE=1: ~2.4s (3 trials: 2433/2414/2478ms)
- 47% reduction in 8gent construction time.
The 8gent still answers turns in lite mode - it just won't sync, learn, or self-heal until you remove the flag. Inspired by jcode's "lean by default" philosophy.
PlanValidateLoop construction from the Agent constructor - the field was assigned but never invoked at runtime, only referenced in a comment. Saves an import chain (tree-sitter + 3 sub-modules) and ~50ms per 8gent instantiation.Agent.chat now walks the failover chain (packages/providers/failover.ts) on Bad Request / 5xx / network / schema / timeout instead of dying. Inner 429 backoff preserved. Hard cap 6 providers, 4 rate-limit attempts. ESC propagates. Exhausted chain throws one composite error listing each attempted provider.desktop_screenshot, desktop_click, desktop_type, desktop_press, desktop_scroll, desktop_drag, desktop_hover, desktop_windows, desktop_clipboard. Each delegates to packages/computer/bridge.ts so security caps stay in one place. Also extended CORE_TOOLS so local providers (LM Studio, Ollama) ship hands too.propose_skill_creation AI SDK tool + packages/self-autonomy/skill-creator.ts. 8gent decides when a recurring pattern is worth capturing, writes packages/skills/<name>/SKILL.md, logs to ~/.8gent/auto-created-skills.jsonl with exact revert command. Hard cap 3 per session, name-collision guard, path-traversal blocked.packages/board-vessel/agent-runner.ts exposing runOfficerAgent({code, task}) that uses the real runAgent from packages/ai with the AI SDK tool registry. Job-spec system prompt with explicit acceptance criteria + prohibited words ("visionary", "empower", "strategic alignment"). Local CLI bun scripts/run-officer.ts 8TO "<task>" for testing.X/Y agents slot now driven by a live HTTP probe (apfel /health, LM Studio /v1/models, Ollama /api/tags), refreshed every 8s.bun run test:models and bun run test:models:providers smoke each role's provider+model with a single prompt and report a pass/fail table. Exits non-zero so regressions in role-registry edits surface immediately.apfel (https://github.com/Arthur-Ficial/apfel) is now a first-class AI SDK provider. Default base URL http://localhost:11500/v1 (override APFEL_BASE_URL), token via APFEL_TOKEN. Model-loader fetches /v1/models instead of falling through to a placeholder./spawn claude / /spawn codex / /spawn hermes / /spawn openclaw / /spawn aider / /spawn 8gent opens a chat tab whose backing brain is the named CLI. Each turn spawns the CLI with the prompt + history; stdout becomes the assistant message. Auth is delegated to each CLI's own login flow - 8gent stays auth-free.chat and run subcommands are pipe-friendly, so /spawn 8gent runs a sibling 8gent worker as a sub-8gent in another tab.8GENT_NO_INTRO=1.packages/skills/touchdesigner/SKILL.md teaching the 8gent to drive Derivative TouchDesigner via OSC, Web Server DAT, or the td Python module. Covers audio-reactive scenes, MIDI-triggered particle bursts, projection mapping, MIDI controller surfaces, and a verification recipe for "is TD reachable from this script?"packages/telegram-bot/: task-runner (lifecycle), mobile-formatter (concise tool summaries + chunking), file-sender (sendDocument/sendPhoto for screenshots and code files), keyboards (Cancel / Retry / Continue / Approve), session-store (per-chat persistence at ~/.8gent/telegram-sessions.json), daemon-client (WebSocket wrapper with reconnect), and bridge-adapter (event glue). packages/daemon/telegram-bridge.ts opts into the new path by default; set EIGHT_TG_LEGACY=1 to fall back. New /cancel command stops the in-flight task. 38 unit tests covering the 5-step end-to-end flow with a fake socket. See docs/specs/TELEGRAM-MULTISTEP.md.the值 in a JSDoc comment for None.flatMap(), last trace of the merge corruption that #1893 cleaned up in identifiers. bun run typecheck and bun test packages/ apps/ (243 + 16 pass, 0 fail) confirm the repo-health work tracked in #1883 is complete.bun test to packages/ and apps/ only, excluding benchmarks/ autoresearch-loop validators that depend on dynamically-generated code in benchmarks/autoresearch/work/ (which only exists at benchmark runtime, not in CI). Restored 241 pass / 10 skip / 0 fail from 243 pass / 21 skip / 439 fail / 16 errors. Added test:benchmarks script for running them explicitly when the work dir is populated. CI uses bun run test to honor the package.json scope.structured-log.ts referenced this.current位 (CJK garbage from a bad merge) instead of this.currentLevel. test-runner.ts returned total意图 instead of totalDuration. Both were causing tsc --noEmit to fail on main, blocking CI on every open PR. First payment toward the broader typecheck cleanup tracked in #1816.packages/eight/loops/computer-use.ts with channel-aware failover (default vision/tool tier = Qwen 3.6-27B), accessibility-tree-first perception (packages/eight/perception/tree.ts) with screenshot escalation (packages/eight/perception/screenshot.ts), Qwen-tuned vision prompt template (packages/eight/prompts/computer-use-vision.ts) with graceful no-vision fallback for the heavy cloud tier, system prompt at packages/eight/prompts/computer-use-system.ts, AppKit-based AX tree query (packages/daemon/tools/accessibility-tree.ts + Swift CLI helper at apps/8gent-computer/Sources/AccessibilityTreeCLI/main.swift) replacing the Phase 1 stub, and an 8-task headless smoke suite (packages/eight/scripts/computer-use-suite.ts) that runs in CI on every PR. NemoClaw policy gate preserved on the production hands path (#1864, #1865, #1866, #1867, #1882)apps/8gent-computer/ Swift package. NSApplication accessory shell, Cmd+Opt+Space global hotkey via NSEvent monitors (no Accessibility prompt), glass NSPanel anchored 80px from the bottom, static AudioWaveView placeholder, headless CLI --headless --intent "..." emitting structured JSON. macOS CI job builds the Swift target and runs the headless smoke (#1857, #1858, #1859)SFSpeechRecognizer streaming captions with mic + speech permission (SpeechCapture.swift), URLSessionWebSocketTask client connecting to the daemon /computer route with reconnect + exponential backoff (DaemonClient.swift), AVSpeechSynthesizer sentence-buffered TTS with hotkey-press interrupt (SpeechReply.swift), daemon protocol v1 wire types (Models/Event.swift), in-panel approval sheet for NemoClaw prompts, real HeadlessMode.swift that connects to the daemon and emits NDJSON token/tool/done events on stdout, mock daemon (scripts/mock-daemon.ts) used by the macOS CI swift smoke job (#1860, #1861, #1862, #1863)packages/memory/computer-use-traces.ts API with startTrace, appendStep, closeTrace, getTrace, listRecent, purgeOlderThan, backed by SQLite migration 001-traces.sql (tables computer_use_traces + computer_use_trace_steps, FK cascade, ordered step indexing). Screenshots written to ~/Library/Application Support/8gent/traces/<sessionId>/<step>.png; rows hold the path. Local-only in v0, no sync. Headless smoke at packages/memory/scripts/smoke-trace-capture.ts. Trace viewer CLI at packages/memory/scripts/traces.ts with list, show, replay (protocol-version-1 NDJSON), and purge --older-than (#1868, #1869)FullDuplexProvider interface with machine-aware backend detector in @8gent/voice (#1252)@8gent/orchestration (#1294)packages/skills/*/SKILL.md and .claude/skills/*/SKILL.md. Supports triggers, aliases, and /alias resolution/commands resolve to loaded skills automatically. Ghost completions include all skill triggers--provider=, --model=, --yes flag parsing. Implicit tui subcommand when first token is a flag8gi-governance, media assets to 8gent-world. This repo is kernel-only^A toggle now fully disables/enables all motion#!/usr/bin/env bun prepended twice, breaking execution on Linux. Now checks if shebang exists before prepending. This fix is critical for all Linux users and Docker-based benchmarks.sed -i '' with cross-platform Node one-liner.benchmarks/harbor_adapter/) - Runs 8gent through Terminal-Bench 2.0 via Harbor framework. Oracle baseline validated at 80%.packages/computer/) - Desktop automation via usecomputer bridge. Screenshot, click, type, press, scroll, drag, hover, clipboard, window list. Security-gated with point validation, max limits, dangerous key detection.packages/computer/process-manager.ts) - Process listing with memory/CPU, graceful/force quit, safe list, system-critical protection (22 blocked processes), suggest quittable apps.desktop_screenshot, desktop_click, desktop_type, desktop_press, desktop_scroll, desktop_drag, desktop_hover, desktop_windows, desktop_clipboard, desktop_processes, desktop_quit_app, desktop_suggest_quit, desktop_safe_list.apps/clui/src-tauri/src/lib.rs) - Daemon status, session count, Resource Manager submenu, Settings submenu with daemon control, log viewer, config access.docs/DAEMON-PROTOCOL.md) - WebSocket protocol specification for external clients (8gent.app, 8gent OS, Telegram). Defines connection handshake, auth, session lifecycle, prompt/response streaming, cron management, and health checks. The contract between the brain and the interfaces.packages/daemon/gateway.ts) - New WebSocket message types: sessions:list, cron:list, cron:add, cron:remove, health. External clients can now manage cron jobs and query daemon state.packages/daemon/index.ts) - Saves active session metadata to ~/.8gent/daemon-state.json on graceful shutdown. Clients can resume sessions after daemon restart.packages/daemon/agent-pool.ts) - Sessions idle for 30+ minutes are automatically evicted. Cleanup runs every 5 minutes.packages/daemon/index.ts) - Bun.write() was overwriting the log file on every event instead of appending. Switched to appendFileSync().packages/validation/security-scanner.ts, secret-patterns.ts) - Static security scanner: detects leaked secrets (API keys, AWS credentials, DB connection strings, JWTs, private keys) and vulnerability patterns (eval injection, SQL concat, innerHTML XSS). scanFile, scanContent, scanDirectory API. Pre-commit gate via hasCriticalFindings. Credit: 0din-ai/ai-scanner for pattern taxonomy.packages/validation/ability-scorecard.ts) - Measurable metrics per ability: memory recall accuracy, worktree parallelization efficiency, policy violation rate, evolution improvement delta, healing recovery rate, entrepreneurship hit rate, AST blast radius accuracy, browser research relevance. JSONL persistence per session, baseline comparison.benchmarks/autoresearch/meta-optimizer.ts) - Optimizes beyond system prompt: mutates few-shot examples, model routing priority, grading weights (exec/keyword split), and temperature sweep. Heuristic suggestions based on what worked per category. Inspired by Karpathy's program.md meta-optimization concept.packages/orchestration/macro-actions.ts) - Coarse-grained task delegation: topological sort on dependencies, parallel group detection, critical path analysis, speedup estimation. Kahn's algorithm for wave-based parallelization.packages/tools/actuators/) - Write actuators for the physical/digital world: deploy (Vercel, Railway, Fly.io), publish (npm, git tag, GitHub release), notify (Telegram, GitHub issues). Dry-run by default. All actions return undo commands where reversible.packages/orchestration/throughput-tracker.ts) - Global tokens/sec metric across all parallel 8gents. Sliding window snapshots, daily reports, per-8gent utilization, model and category breakdowns. JSONL persistence with 7-day retention.packages/eight/curriculum.ts) - Teachable curricula with step progression, exercises, and comprehension checks. Built-in: "8gent-architecture" (5 steps) and "writing-benchmarks" (4 steps). CurriculumRunner generates teaching prompts.packages/self-autonomy/persona-mutation.ts) - Auto-tune SOUL.md calibration table from accumulated user feedback. Parses current persona parameters, records up/down feedback with evidence, suggests mutations (each feedback = +/-5, clamped 0-100). Never writes to SOUL.md directly (safety constraint).packages/telegram-bot/unified-portal.ts) - Single portal to all automation: /status, /agents, /benchmark, /deploy, /throughput, /scorecard, /soul, /help. Auth gating, inline keyboards, auto-split for long messages. Stub handlers document integration points.packages/memory/) - SQLite + FTS5 persistent recall with Ollama embeddings, 30-day decay, frequency-based promotion via PromotionManager, semantic search via SemanticRecallpackages/orchestration/) - WorktreePool for multi-8gent parallel execution via git worktrees, max 4 concurrent 8gents, filesystem-based inter-8gent messagingpackages/permissions/) - YAML-driven policy engine with 11 default rules, approval gates for destructive operations, privacy-aware model routingpackages/self-autonomy/) - Post-session reflection, Bayesian skill confidence scoring, self-improvement SQLite database, learns from successes and failurespackages/validation/) - Hypothesis Loop pattern: checkpoint-action-verify-revert. Git-stash atomic snapshots, failure log (~/.8gent/healing/failures.jsonl), configurable retry limitspackages/proactive/) - GitHub bounty and help-wanted issue scanner, capability matcher, opportunity pipeline with full lifecycle trackingpackages/ast-index/) - Blast Radius Engine: import dependency graph, test file mapping, change impact estimation before any editpackages/tools/browser/) - Lightweight web access via fetch + DuckDuckGo HTML scraping, disk cache, no headless browser dependenciessay command with sentence-chunked delivery for natural speechVoiceChatLoop class (packages/voice/voice-chat.ts), useVoiceChat React hook/github slash command, gh CLI auto-configovernight-competition.ts, overnight-orchestrator.sh, sync-results.tstenants table with CRUD mutations, in-memory fallbackSessionSyncManager batches token/tool-call deltas, flushes every 10swhile(running) with non-blocking setTimeout scheduler; fixed useInput consuming all keyboard inputuseStdout() called conditionally, violating React hook rules; moved to top of componentagent.chat() doesn't update React state; added explicit setMessages() callsmarginLeft={20} replaced with responsive terminal-width-based indentPreferencesSyncManager pulls/pushes preferences via Convex after auth; updatedAt wins merge strategyUSER_CONTEXT_SEGMENT injects user name, role, communication style into system prompt/history, /continue, /resume, /compact slash commands; checkpoints every 5 messagesPersonalCollector quality-filters session traces for fine-tuning (score >= 0.7, no corrections)userId field on MemoryBase and SearchOptions for per-user memory recalldocs/PERSONALIZATION.md covering all 5 phases8gent auth login), macOS Keychain token storage with AES-256-GCM encrypted file fallback, JWT validation via jose, automatic token refresh, non-blocking anonymous mode (auth never blocks local usage)useVoiceInput hook (Ctrl+Space toggle) and VoiceIndicator component with recording status, audio levels, and download progress8gent auth login, 8gent auth logout, 8gent auth status, 8gent auth whoamidocs/bmad//vision status, /vision model <name>, /vision ocr <name>, /vision pull for recommendationsdefaultModel, ocrModel, fallback chains, provider preference (local/cloud), timeoutfindOCRModel() prefers dedicated OCR models for text extraction, falls back to general vision models with strong OCRcolor="gray" and color="white" with dimColor and default text in apps/installer/src/index.tsx/8gent-code/server.ts that resolve outside the working directory; these are now auto-stripped to relative paths instead of throwing a path-traversal error; tool description and system prompt updated to instruct models to use relative pathsgetBestFreeModel() in packages/providers/index.ts queries OpenRouter's /api/v1/models endpoint to find the best available free model (filtered by :free suffix, sorted by context length); results cached for 1 hour; spawn_agent now accepts model: "auto:free" to automatically pick the best free model/evidence command shows full session breakdown with per-type countsversion-manager.ts) - manages model promotion lifecycle with naming convention eight-{major.minor.patch}-q{gen}:{params}, Gemini Flash judge validates checkpoints before promotioneight-1.0-q3:14b is now the primary recommended model across all documentation and quick-start guides/model command now accepts arbitrary model identifiers, not just predefined optionsproxy.ts) - start/stop training proxy, health checks, latency overhead monitoring with configurable threshold - Phase 2: Judge scoring (judge.ts) - PRM wiring via Gemini Flash (free), score distribution tracking, per-model stats, daily trend analysis - Phase 3: Training orchestration (training.ts) - GRPO batch collection with score filtering, checkpoint creation, benchmark validation gate, auto-rollback on regression - Phase 4: Production loop (loop.ts) - MadMax scheduling (sleep/idle windows), auto-promotion of improved checkpoints into model-router, health monitoring, score trend alerts - Kernel manager (manager.ts) - unified entry point, reads .8gent/config.json, safe no-op when disabledTRAINING_PROXY_URL env var and .8gent/config.json training_proxy section to route Ollama calls through the OpenAI-compatible training proxybenchmarks/autoresearch/validate-checkpoint.ts runs benchmark suite against fine-tuned models and compares against baseline scores to prevent regressionsapps/demos/) - React-based video generation for product reels and landing page content - 3 ready-to-render compositions: HeroIntro, FeatureShowcase, CostComparison - 9:16 vertical (reels) and 16:9 landscape variants for each - Reusable component library: Logo, TerminalWindow, GlowCard, CodeBlock, Background - Animation utilities: fade-in, scale-in, typewriter, glow pulse, counter - Branded design tokens matching 8gent visual identity - Scripts: studio, render:hero, render:features, render:cost, render:all - Media preview page (bun run demos:media) - Vite-powered browser preview with Remotion Playercreative, research, communication, planning to StepCategorypredictCreativeSteps() and predictResearchSteps() for non-code tasks/board (kanban view), /predict (confidence-scored predictions), /momentum (velocity stats)EvidenceCollector constructor now accepts optional config with process.cwd() default (was required, crashed without args)PredictionContext.currentPlan type inlined (was referencing undefined ExecutionPlan)indexRepo() now throws descriptive error instead of generic "Not implemented"...config spread in EvidenceCollector that was overwriting defaultsharness run / harness inspect / harness doctor / harness sessions8 CLI alias (short for 8gent)packages/agent/ renamed to packages/eight/| Benchmark | Domain | Score |
|---|---|---|
| BT001 | Auth System | 94 |
| BT002 | Event Architecture | 92 |
| BT003 | Data Pipeline | 100 |
| BT005 | State Machine | 92 |
| BT007 | SEO Audit | 96 |
| BT011 | Video Production | 100 |
| BT012 | Music Theory | 81 |
| BT014 | AI Consulting | 95 |