From 4809d781ff0b8b9d4329f2b5020ac061c41bf0bd Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Fri, 17 Jul 2026 14:03:23 -0400 Subject: [PATCH] feat: deployment-level no_shell policy (agents.yaml no_shell: true) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fast-agent auto-activates its shell execute tool on any agent with skills configured, and Context.no_shell — the only opt-out — has no constructor or config knob. Add install_no_shell(), a wrapper on fast_agent.context.initialize_context that stamps no_shell=True on every context, installed from server.main() when the deployment's agents.yaml sets a truthy top-level no_shell: key. Skill loading via read_skill is unaffected. Default behaviour unchanged for deployments without the key. Co-Authored-By: Claude Fable 5 --- pallas/_fastagent_patch.py | 33 +++++++++++++++++++++++++++++++++ pallas/server.py | 8 ++++++++ pyproject.toml | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pallas/_fastagent_patch.py b/pallas/_fastagent_patch.py index f6d9a49..c13c37a 100644 --- a/pallas/_fastagent_patch.py +++ b/pallas/_fastagent_patch.py @@ -474,3 +474,36 @@ def install() -> None: _patch_create_session_factory() _patch_aggregator_call_tool() + +def install_no_shell() -> None: + """Disable fast-agent's shell runtime for every agent in this process. + + fast-agent auto-activates its ``execute`` shell tool on any agent that + has skills configured (``MCPAgent._ensure_shell_runtime_for_skills``); + the only opt-out is ``Context.no_shell``, which fast-agent's own CLI + sets by direct assignment but which has no ``FastAgent``-constructor or + config-file knob. The context is created inside ``run()`` before agents + are constructed, so we wrap ``initialize_context`` to stamp the flag on + every context it returns. Skill loading itself is unaffected — the + ``read_skill`` tool registers independently of the shell. + + NOT installed by ``install()``: this is deployment policy, not a runtime + fix. ``server.main()`` calls it when the deployment's ``agents.yaml`` + sets a truthy top-level ``no_shell:`` key. + """ + from fast_agent import context as _fa_context + + if getattr(_fa_context.initialize_context, "_pallas_no_shell_patched", False): + return + + _original_initialize_context = _fa_context.initialize_context + + async def _initialize_context_no_shell(*args: Any, **kwargs: Any): + ctx = await _original_initialize_context(*args, **kwargs) + ctx.no_shell = True + return ctx + + _initialize_context_no_shell._pallas_no_shell_patched = True # type: ignore[attr-defined] + _fa_context.initialize_context = _initialize_context_no_shell + logger.info("no_shell patch installed — agents will not get the shell tool") + diff --git a/pallas/server.py b/pallas/server.py index 2aa481a..b11d4e9 100644 --- a/pallas/server.py +++ b/pallas/server.py @@ -18,6 +18,7 @@ from pathlib import Path import yaml +from pallas import _fastagent_patch from pallas.log import set_agent_component, set_project, setup_logging from pallas.multimodal_server import MultimodalAgentMCPServer @@ -390,6 +391,13 @@ def main() -> None: setup_logging() + # Team-level policy: a truthy top-level ``no_shell:`` in agents.yaml + # keeps fast-agent's shell tool off every agent in this deployment + # (it otherwise auto-activates on agents with skills configured). + # After setup_logging() so the install log record isn't lost. + if config.get("no_shell"): + _fastagent_patch.install_no_shell() + if args.agent: port = agents[args.agent]["port"] logger.info("Starting %s agent on port %d", args.agent, port) diff --git a/pyproject.toml b/pyproject.toml index 9eaf7a8..0a6966d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pallas-mcp" -version = "0.5.1" +version = "0.5.2" description = "FastAgent MCP Bridge — generic runtime for serving FastAgent agents over StreamableHTTP" requires-python = ">=3.13.5" dependencies = [ -- 2.43.0