Repository navigation
0.5.0 (5/5): dg mcp, agent mode, CLI and config fixes, and the 0.5.0 release marker - #164
Closed
dg-coreylweathers wants to merge 25 commits into
Closed
dg-coreylweathers wants to merge 25 commits into
dg-coreylweathers wants to merge 25 commits into
Conversation
The base runs dg mcp from a minimal copy of the vendored proxy in deepctl_cmd_mcp.proxy. dg mcp's fast path and the command now share it, and the port is finished: - initialize and tools/list come from one fixed local instructions and tool table, in deepctl.mcp._fast; the proxy's own copy goes, so the two paths can't drift; - the fast path's hand-off serves through the proxy (its LineWriter drops the reply to the replayed initialize) instead of its own adapter, so nothing connects until a tool call and a remote failure is that call's tool error; - key lookup (key_or_error) lives in the hand-off module and serves both paths: no key, an unreadable config or a config key an agent session doesn't trust give the same text; - the HTTP client takes the transport's no-cookies setting; it still follows no redirects, and a 3xx keeps core's refusal text. The base's tests that duplicate these now live in test_handoff.py; the rest stay in test_proxy.py, pointed at the shared table. The base's end-to-end tests (with the --debug and same-origin redirect cases) are kept as they are.
Every dg mcp run that serves (not --help or --agent-friendly), with or without root options before mcp, now moves fd 1 to stderr before Click or any command code loads. The MCP server writes to a private copy of the original stdout, so a stray print, a library warning, a config notice or a child process can no longer corrupt the client's JSON-RPC stream. main() recognises `dg [root options] mcp` without Click; a test keeps its list of value-taking root options equal to the root group's.
The help, the READMEs, llms.txt, llms-full.txt and the landing page promised SSE and streamable HTTP transports, "every CLI command" as tools and direct API access, and one README put the API key in a project .mcp.json's args. Agents follow this text literally. Now they all say what the command does: a local stdio MCP server with one tool, search_deepgram_knowledge_sources, that searches Deepgram's documentation with the key from dg login or DEEPGRAM_API_KEY. The key never goes in the MCP config, a missing key is a tool error rather than a failed start (or, for a config file dg can't trust, an error that asks a person to fix it), and Claude Desktop's literal env values are called out. The --base-url and --debug help match their behaviour, including https unless the host is loopback. The help spec is regenerated with --write-help; the cmd-mcp README matches generate_readmes.py output for the new description. A test fails on MCP text that promises what dg mcp doesn't do. It scans the READMEs, web/public, web/src and the sources (help specs included) for: - a transport other than stdio in any spelling: --transport sse, --transport=http, -t sse, "--transport", "sse" in args, an /sse URL, streamable-http, "stdio/SSE"; - a --stdout option; - an API key on a command line (--api-key KEY, --api-key=KEY, a variable) or in an MCP config's args array, however it is wrapped. Only the API-key checks skip `dg login --api-key ...`, which gives the key to the command that stores it; every other check still applies on that line. Parametrized cases cover each variant and the true text that must pass.
deepctl_core.agents is now the only place dg decides whether an AI agent, automation or a person is running it. Each hard signal names the public source that documents or implements it, and the table grows from 9 variables to 21 rows: AI_AGENT first, then AGENT (amp, goose), GOOSE_TERMINAL, the Claude Code, Codex, Gemini, Copilot CLI, OpenCode, Antigravity, Augment, Cursor and Aider variables, the /opt/.devin file and a .pi/agent entry in PATH. A variable counts when it is set and not empty; whitespace still counts, as before. DG_AGENT=1 turns agent mode on and DG_AGENT=0 turns it off for output, prompts, hints and login. Trust decisions keep reading the raw markers, so DG_AGENT never loosens or tightens which config file and key dg uses. Every new marker therefore also applies the agent-session config and base URL rules. NO_COLOR no longer counts toward the no-terminal score. Under NO_COLOR, and in agent mode, the consoles write plain text with no colour, bold or dim; otherwise they never pass no_color=False, so FORCE_COLOR and TERM work as rich reads them. CI matches case-insensitively, and the command-line flags count only before "--". output.is_agentic(), output.is_ai_agent() and AI_AGENT_ENV_MARKERS stay importable and keep their meaning. output gains invoked_prog() and stderr_color() for messages that name the program or colour raw stderr lines.
"dg help agent-mode" prints, as plain text and offline: - the detection table, each row with the sources that document it; - which row this process matched, and why agent mode is on or off; - how a person removes a stray marker in sh/bash/zsh, fish, PowerShell and cmd, and DG_AGENT=0 for the markers that can't be unset; - the trust rules in agent sessions: a user config that others can change or another user owns is ignored, its API key unused and never saved to, DG_AGENT=0 doesn't change that, and the person runs chmod 600 on it. "dg help" is a new command group from the root distribution, added as manifest fragments with root entry points. The README points to the topic instead of listing a few variables.
Every notice now goes to stderr, prints once, and names the program the way it was invoked: - A person at a terminal whose environment carries an AI-agent marker gets one INFO line saying why dg is in agent mode and pointing to "dg help agent-mode". It names no shell command, and it never prints when DG_AGENT decides the mode or without a terminal. - The skills prompt, the update notice and the plugin update notice no longer show in agent or automation sessions, use colour only on a person's terminal (NO_COLOR and TERM=dumb turn it off, FORCE_COLOR turns it on), and name the invoked program. - Copying a legacy config is reported through the config notice queue, so it follows the INFO:/WARN: prefixes and --quiet. - dg ffprobe --path reports a bad path through the error console, with the ERROR: prefix in agent mode, once. - The pending-login profile error names the invoked program. - dg --version prints "dg, version ...", "deepgram, version ..." or "deepctl, version ..." as invoked (DEEPCTL_PROG_NAME first, as for every other message), on the fast path and the Click path alike. Changelog: "--version now prints the name you ran (dg, deepgram or deepctl)". The bug report template's example follows. New subprocess tests pin -o json stdout to one JSON document with a malformed or unreadable config, with the warning once on stderr.
A few comments and test docstrings in the config loading, credential and skills code pointed at notes kept outside the repository. They now describe the behaviour under test instead.
On Windows a redirected stdout uses the ANSI code page (cp1252, say). The banner's block art and the check mark aren't in it, so `dg --help > file` and the `dg ffprobe --path` success line stopped with "'charmap' codec can't encode". A stdout or stderr that isn't UTF-8, and whose error handler raises on such characters (strict, or surrogateescape under a C locale), now writes them as backslash escapes. The encoding itself is kept, so a real console keeps the one Python chose, and a UTF-8 stream is untouched.
profiles --list and --current read each profile's key from the keyring and used the config file's key only when the keyring raised. A backend that answers "no entry", as Windows Credential Manager does for a key saved only in config.yaml, showed "API Key: Not set" for a profile that has one. The config file's key is now shown whenever the keyring has none.
On macOS platformdirs puts the user config under $XDG_CONFIG_HOME when it is set, but only ~/Library/Application Support could approve hosts in an agent session. A person with XDG_CONFIG_HOME=~/.config, a common setup, had approved hosts that never counted there. When XDG_CONFIG_HOME is exactly ~/.config under the account's own home (taken from the OS, not $HOME), that file is the one that counts, as on Linux. Any other value still keeps Application Support, so a file elsewhere never counts in an agent session.
A read waits up to 5 s for a writer's lock on the config file, then skips the file with a warning. A command reads the file more than once, so a writer stuck holding the lock made `dg profiles --list` wait two or three times as long (about 16 s on macOS). Once a wait for a file has timed out, later reads of that file in the same run try the lock once and skip it straight away. A run now waits at most one bound.
Flux errors and the speak --model help said "Flux STT (listen v2)", "Speak v2" and "Speak v1": API names nobody types. They now name the model families (Flux models, flux-* and aura-* voices) and say what to do instead.
The stdin banner said to send EOF with Ctrl+D, which doesn't end input in a Windows console; Windows now gets "Ctrl+Z, then Enter". A refused microphone printed PortAudio's text and macOS-only advice. It now uses the catalogue's mic-denied message and hint, plus the setting's place on macOS or Windows. Other microphone errors keep their own text.
Some tests that came from earlier releases measured wall-clock time with bounds a loaded machine can miss. Each check keeps what it proves, with a wider margin: - skills: the lock wait is 2 s, so one wait (2 s) and one per tool (4 s) stay apart; a stale lock is taken well inside a 10 s timeout. - config save: one 5 s lock wait is under 8.5 s; two would not be. - FileInfo creation is measured in CPU time, 2 s for 1000 models. - dg mcp e2e: no connection attempt within 12 s (one waits 15 s).
Ctrl-C and Ctrl-D now print "Cancelled." (exit 2), and an exception nothing else handles prints "Command failed: <message>" with a next step (exit 1). In agent mode both print exactly two prefixed lines, ERROR: and HINT:, with the agent hint from the catalogue. The early Ctrl-C path in main() prints the same lines without rich or Click.
The check reads the SigIgn mask from /proc/self/status, which exists only on Linux, so on macOS and Windows it failed opening the file. It now skips there and, on Linux, always checks the mask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ting it A plugin entry point named like a built-in command was refused before its module was imported, but one named like a built-in alias, such as debug-audio, was imported first and only refused once loaded. The check before import now includes the top-level aliases too, so none of that plugin's code runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dg login checks that the API base URL is trusted before it starts a device login, but dg login --complete went straight to polling and saving the approved key. It now runs the same check first, so an untrusted DEEPGRAM_BASE_URL or --base-url stops it before any request, and the pending login is kept for a retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The speak tests had their own autouse fixture that cleared the agent markers and DG_AGENT, which the root fixtures already clear for every test. It is removed, and the remaining fixture's docstring covers the agent output rules the speak tests relied on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments, docstrings and the manifest schema compared behavior with a version number that was never released. The release they mean is 0.4.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h uv The deepctl-core and deepctl-cmd-debug-probe READMEs didn't list the websockets requirement they now have. make readmes and make readmes-check ran the system python3, which on some machines is too old for the generator (it needs tomllib, Python 3.11+); they now use uv run python, like the other Makefile targets, and the --check message points at make readmes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/telemetry.md described error reports as sent by default. This version has no built-in destination: the reporting code runs only with a loopback DEEPCTL_TELEMETRY_DSN, for tests and local development. The page now says so up front, and describes the switches, DEEPCTL_TELEMETRY=log and the field schema as they apply to a loopback sink. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test removed a built-in's package by pointing its manifest entry at a module that doesn't exist, but plugin_manager had already read the built-in module table from the real manifest. The module then wasn't the built-in's own, so the check that runs before import refused it as a plugin replacing a built-in, and the test never reached the dependency_missing error. That order is right: a module that isn't the built-in's own is never imported. The test now patches both tables, as an uninstalled package leaves them, and gets dependency_missing again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
python -m deepctl_cmd_mcp still parses the transport names an old MCP config may use, streamable-http among them, so it can print the stdio-only error instead of an argparse error. The lint that keeps shipped text from promising other transports flagged that refusal. It now skips that file for that one check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This release adds features and changes defaults that need upgrade notes, so it is a minor release. With bump-patch-for-minor-pre-major set, release-please would otherwise propose a patch release. Release-As: 0.5.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dg-coreylweathers
requested review from
GregHolmes and
deepgram-kiley
as code owners
November 7, 2026 02:36
Contributor
Author
|
Consolidated into #165, which has all 74 commits of 0.5.0 in one PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 5 of 5 of the 0.5.0 release, stacked on part 4. Review in order. Merge only after the PR below it has merged, and only after 0.4.1 is released.
dg mcpshares one proxy and tool table and keeps stdout for JSON-RPC from the start. AI agents are detected from one documented table, anddg help agent-modeexplains agent mode. Warnings stay off stdout, config fixes (XDG on macOS, locked config files), Flux model naming, per-OS stdin and microphone help, and coded messages for cancel and unexpected errors. Ends with review fix-ups (a plugin named like a built-in alias is refused before import,dg login --completechecks the base URL, test and README fixes) andchore: release 0.5.0, which carriesRelease-As: 0.5.0.Commits
Notes
Release-As: 0.5.0. Without it, release-please would propose a patch release instead of 0.5.0.🤖 Generated with Claude Code