Skip to content

Latest commit

 

History

History
107 lines (81 loc) · 17.4 KB

File metadata and controls

107 lines (81 loc) · 17.4 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog.

[Unreleased]

0.4.0 - 2026-09-01

Added

  • New mcp object in knosh.json declares local MCP (Model Context Protocol) servers, modeled on OpenCode's local server schema: type (must be "local"), command (argv array) or commandLine (a single string, tokenized POSIX-style), enabled (default true), environment, and timeout (seconds). This is configuration and inspection only — servers are not yet launched or connected to any agent. See the configuration reference.
  • New list-mcp command lists all servers configured under mcp, annotating disabled ones and showing an environment-variable count (never names or values). See list-mcp.
  • Agent frontmatter now supports an mcp: block that overrides the global enabled state of MCP servers per agent, keyed by exact server handle or glob, with exact-beats-glob and last-matching-glob-wins precedence. list-mcp --agent <id> (or the resolved defaultAgentId, when set) shows each server's global state alongside its effective state for that agent. See mcp frontmatter and list-mcp --agent.
  • MCP servers are now actually launched: for each prompt, kdocs, or file-defined command run, every effectiveEnabled MCP server is spawned over stdio and its MCP handshake completed before the run starts, and every process Knosh started is terminated when the run finishes (success or failure). mcp.timeout (seconds) now bounds spawn-plus-handshake, defaulting to 5 seconds when unset, and a new mcp.required field (default false) controls what happens when a server fails to start: a warning on stderr and the run continues, or, when true, the run aborts. See the configuration reference.
  • Each MCP server's tools are now discovered during its startup window and merged into the agent's tool registry, offered to the model alongside Knosh's built-in tools. Each MCP tool is namespaced as <serverHandle>_<toolName> (e.g. ksrc-mcp_search) so it can never collide with a built-in; Knosh built-ins always take precedence on a name collision, and any residual collision is dropped with a warning on stderr. AgentRunConfig.allowedToolIds (used today only by kdocs, to enforce its read-only tool set) filters the MCP registry by the same name rule as built-ins — since no MCP tool name can appear in that built-ins-only allowlist, kdocs continues to offer no MCP tools. MCP tools have no per-tool permission model yet and cannot be named in an agent's tools: allowlist; the existing server-level mcp: enable/disable in agent frontmatter remains the only per-agent gate over them. See the configuration reference, mcp frontmatter, and the Tools reference.
  • MCP servers can now be type: "remote", modeled on OpenCode's remote server schema: a required url and optional headers object, connected over MCP Streamable HTTP (the deprecated HTTP+SSE transport is not supported, and OAuth is not supported — a server that requires it simply fails to connect and is handled by the existing required policy). A remote server's tools are discovered and namespaced identically to a local server's, and the same enabled/timeout/required fields and per-agent mcp: frontmatter overrides apply to both types. A header value exactly equal to {env:NAME} is resolved from the NAME environment variable at connect time, so a secret like a bearer token never has to be written into knosh.json; an unset variable is a startup failure handled by the required policy, same as any other. list-mcp prints a remote server's url and header count only — header names and values are never shown. See the configuration reference and list-mcp.
  • New add-mcp command writes a new entry under mcp in knosh.json without hand-editing the file, for both local (--commandLine/--command-line, or trailing argv after --) and remote (--url) servers. Server type is inferred, never declared: --url makes it remote, argv or --commandLine makes it local. Also supports repeatable --env KEY=VALUE (local) and --header NAME=VALUE (remote), --timeoutSeconds/--timeout-seconds, --required, --disabled, --global (mirroring init --global), and --force to replace an existing entry — which is otherwise an error. The target file is created if it does not exist. On success, add-mcp prints the file it modified and echoes the JSON of the entry it wrote, wording a --force replacement (Replaced ...) differently from a fresh add (Added ...). See add-mcp.
  • New mcp-info <name> command prints one configured MCP server's list-mcp details, then actually connects — spawns/connects, completes the handshake, discovers tools, and disconnects — to confirm the server is reachable and list what it exposes. It never executes a discovered tool. Unlike a real run, every startup failure is reported and the command exits 1, rather than being swallowed as a stderr warning; a server that is effectively disabled (globally, or via a per-agent mcp: override resolved through --agent) is refused unless --force is given. Each tool is shown under both its namespaced (<serverHandle>_<toolName>) and raw name; --timeoutSeconds/--timeout-seconds overrides the server's startup budget for the probe, and --verbose adds each tool's required and optional parameters. A server that legitimately exposes zero tools is still a success. See mcp-info.
  • Per-tool permission patterns can now be written relative to the working directory (e.g. "src/*": "allow"), so one agent file works unchanged across every project it is used in. A pattern beginning * or ? is used verbatim (keeping a bare "*" base rule matching every path); a pattern beginning /, ~, or $HOME is already anchored; every other pattern is resolved against the working directory. ~/$HOME are now also expanded in per-tool patterns. See Agent configuration.

Fixed

  • Closed a .. traversal bypass in per-tool permission pattern matching: a pattern was matched against the raw path string the model supplied, so a path like src/../secrets.txt could satisfy an allow rule scoped to src/* even though it resolved outside that directory. Per-tool patterns and received paths are now both resolved and lexically normalized before matching, using the same primitive the external_directory containment check already uses.

Changed

  • Consolidated the duplicated AgentPromptExecutor/sinkFactory/interactive test-seam defaults in knosh-cli into shared, unit-tested factory functions, and extracted main()'s error-handling and console-detection logic into an injectable runKnosh/consoleAvailable seam, so this wiring is now exercised by tests instead of only by production use. Raised koverGate coverage thresholds across knosh-cli, lib/knosh-agents, and lib/knosh-tools to match the resulting measured coverage. No command-line behavior changed.
  • Config file merging now goes one level below the top-level key for mcp, toolLimits, and webFetch, instead of the local file's object replacing the global one wholesale. toolLimits and webFetch now merge field by field. mcp merges per server handle: a handle declared in only one file survives, a handle with a full definition in the local file replaces the global entry for that handle, and a local entry naming only enabled (e.g. {"enabled": false}) overlays the global entry instead — the supported way to disable or re-enable a globally-declared server from a project file. An enabled-only entry with no counterpart to overlay in the other file is now a startup configuration error rather than silently doing nothing useful. See Config file merging.
  • Refreshed dependencies: Koog to 1.2.0 (and its -beta siblings to 1.2.0-beta), Kotlin to 2.4.10, and smaller patch-level bumps to Ktor, Okio, Kover, kotlinx-io, and kmpLogger (to its 2.0.0 major release, which is behavior-preserving on the JVM). Two dependencies are deliberately held back: sort-dependencies stays at 0.19.0 pending a fix for a known upstream regression, and the MCP Kotlin SDK stays at 0.11.1 because Koog's agents-mcp still pins kotlin-sdk-client to that version.

0.3.0 - 2026-07-18

Added

  • A new --benchmark flag on prompt, kdocs, and file-defined commands prints a metrics summary to stderr after the run, showing token consumption, LLM call count, tool call count, and elapsed time. Example: 42,318 in / 1,205 out tokens · 6 LLM calls · 3 tool calls · 12.4 seconds. The summary appears only when --benchmark is set and stderr is an interactive terminal. For commands that run multiple iterations (e.g., kdocs over several files), a single summary is printed at the end with token and call counts summed across all iterations. Token counts are omitted when the provider does not return usage metadata.
  • A live status line is shown on stderr while an agent runs in an interactive terminal, cycling a Braille spinner through starting…, calling model…, and running tool: <name> as the run progresses. It is omitted when iterations run in parallel (--parallel greater than 1).
  • New defaultAgentId field in knosh.json sets a project-wide fallback agent. When set, --agentId becomes optional for prompt, kdocs, and file-defined commands that do not declare agent: in their frontmatter.
  • New toolLimits object in knosh.json makes the maximum size of tool responses configurable: globMaxFiles (default 100), grepMaxMatches (default 100), textReadMaxBytes (default 51200), and webFetchMaxBytes (default 131072). Each field name carries its unit of measure, and each value must be greater than zero. See the configuration reference.
  • New toolLimits.grepMaxLineLength (default 100000 characters) and toolLimits.grepTimeoutMillis (default 5000 ms) fields bound the grep tool's regex matching: lines are matched only within the first grepMaxLineLength characters, and the whole search aborts after grepTimeoutMillis of wall-clock time, returning partial results with a note. See Limits.
  • prompt is now the default command. When the first argument is not a recognized subcommand name or a root-level flag (--help, --version), Knosh routes the invocation to prompt automatically. For example, knosh "summarize this" is equivalent to knosh prompt "summarize this", and knosh --agentId helper "hello" is equivalent to knosh prompt --agentId helper "hello". Running knosh with no arguments still prints root help.
  • New webFetch object in knosh.json configures the SSRF egress policy for the web-fetch tool: allowedHosts (default [], meaning any host) restricts fetches to an explicit host allowlist, and allowInternalHosts (default false) controls whether a request may resolve to a loopback, link-local, site-local, any-local, multicast, or IPv6 unique-local address. See the configuration reference.
  • Knosh now warns on stderr at startup if the global config file (~/.config/knosh/knosh.json), its parent directory, the local config file (.knosh.json), or its parent directory is writable by other users on the system — a world-writable config lets a local attacker rewrite trusted settings such as the webFetch allowlist. The check is advisory only (Knosh does not refuse to start or alter permissions), flags only the world-writable bit (not group-writable), and is a silent no-op on non-POSIX filesystems. See File permission advisory.

Fixed

  • Hardened the grep tool against pathological caller-supplied regexes (ReDoS): a wall-clock time budget (toolLimits.grepTimeoutMillis) can now abort a match already in progress, and a per-line input-length cap (toolLimits.grepMaxLineLength) bounds the characters fed to the matcher. A search that exceeds its time budget returns partial results with a note rather than hanging or erroring.
  • Closed a path-traversal gap in filesystem-tool permission checks: .. segments (and un-expanded ~/$HOME) in a tool's path argument could previously reach files outside the intended boundary, because the permission check and the actual file resolution derived different values from the same raw string. Path resolution and normalization are now unified behind a single primitive shared by both the permission check and the filesystem tools, and the external-directory boundary is now containment-based: a path that resolves inside the working directory is always allowed, and a path that resolves outside must match an external_directory rule (see Agents). Symlink (real-path) canonicalization is a deferred follow-up — this fix normalizes lexically only.
  • Hardened the web-fetch tool against SSRF: requests are now checked against a host allowlist (when configured) and, by default, refused if they resolve to a loopback, link-local (including the 169.254.169.254 cloud-metadata address), site-local, any-local, multicast, or IPv6 unique-local address. The tool now follows redirects manually instead of relying on automatic client redirects, re-checking every redirect hop against the same policy (capped at 5 hops), so an allowlisted URL cannot redirect its way into a disallowed internal address. Configurable via the new webFetch object; see Security / SSRF. DNS-rebinding / TOCTOU hardening is a documented, deferred limitation — not addressed by this fix.

Changed

  • The web-fetch tool now caps the returned content (after HTML-to-Markdown/text conversion) at webFetchMaxBytes UTF-8 bytes — 128 KB (≈32K tokens) by default — instead of capping the raw download at 5 MB. A 5 MB internal limit still bounds how many raw bytes are read from the network.

0.2.0 - 2026-06-19

Added

  • File-defined commands, where you can wrap up a prompt with some YAML frontmatter and be able to readily reuse that prompt. See the documentation for more.
  • knosh update will install pre-packaged file-based commands, or update them with newer ones from the current installed copy of Knosh.
  • Proof-of-concept review-plan and review-gradle-license file-defined commands are packaged in this version of Knosh.
  • New commandsDir field in knosh.json to relocate the commands directory.
  • list-tools command to list all registered tools, their write-capability, and per-agent access rules

Changed

  • Breaking: knosh kdocs --parallel is now an integer-valued option (--parallel N) instead of a boolean flag. Valid range is [1, 32]; the default when omitted is 1 (sequential, equivalent to today's behavior when --parallel was absent). Invocations that previously passed --parallel without a value will fail with a parse error and must be updated to --parallel N.

0.1.0 - 2026-05-25

Added

  • init --global now creates an empty agents/ directory at ~/.config/knosh/agents/ in addition to writing the config file
  • init command to write a default knosh.json config file, with --global flag for writing to ~/.config/knosh/knosh.json
  • list-agents command to list available agents in the configured agents directory
  • kdocs command to iterate over supplied source files and update their KDoc comments using an LLM
  • prompt command (formerly agent) to run a one-shot LLM prompt using a named agent
  • Agent configuration via Markdown files with YAML frontmatter (model, temperature, tool permissions, system prompt)
  • Twelve built-in tools: file-info, get-working-directory, glob, grep, list-directory-tree, text-read, web-fetch, create-directory, delete-file, move-content, text-edit, text-write
  • Support for multiple LLM providers via Koog: Anthropic, Mistral, OpenAI, and Ollama
  • Per-agent and per-command temperature overrides via --temperature CLI option
  • --max-iterations option to cap the number of LLM tool-call cycles per run
  • Retry policy for transient LLM and network errors, including Ollama-specific retries
  • Tool-call logging with optional full-content logging via --log-full-tool-calls
  • --log-full-prompts option to log full prompt content for debugging
  • Rich terminal output via Mordant (Markdown rendering in TTY; plain text elsewhere)
  • Global and project-local knosh.json config with optional per-provider API keys
  • Functional test suite running against a local Ollama instance