From 455bee7191e13100956ac16b84fcc49e449292bc Mon Sep 17 00:00:00 2001 From: S'Bussiso Dube <80188685+Sbussiso@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:02:44 -0700 Subject: [PATCH] Wire the agent to Command Center's secrets instead of duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems the first deploy of the agent process group exposed. 1. The agent would have failed every tool call. Command Center's MCP server authenticates the agent against SENTINEL_AGENT_MCP_KEY, while the agent presented OPENSENTRY_MCP_AGENT_KEY. Different names, and the first-party deployment deliberately uses two DIFFERENT shared secrets for the run-queue and the MCP surface. So with no explicit value set, the existing self-hosted fallback would have handed the MCP surface the run-queue key — an agent that fetches work and then fails every tool call, which is precisely the failure that fallback exists to prevent for self-hosters. Now that the agent runs in the sentinel-command app it shares that app's environment, so it reads Command Center's own variable via an AliasChoices: OPENSENTRY_MCP_AGENT_KEY first, then SENTINEL_AGENT_MCP_KEY, then the self-hosted fallback. No duplicated secret. Precedence verified in all three directions, including that the self-hosted path still resolves to the agent key. 2. Fly provisioned TWO agent machines. That is its default for a newly-declared process group, and for this group it is actively wrong: two workers drain the same queue, and POST /start is idempotent rather than exclusive, so both would claim the same run and we would pay for the LLM call twice. Scaled back to one, and the deploy now passes --ha=false so it cannot recur. OLLAMA_API_KEY and OLLAMA_MODEL are staged on sentinel-command, moved from the old app without their values passing through a terminal. Co-Authored-By: Claude Opus 5 --- .github/workflows/deploy.yml | 8 +++++++- backend/app/sentinel_agent/config.py | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 504e20a..5fdcae3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -327,8 +327,14 @@ jobs: # single-attachment volume: rolling tries to stand up a parallel # machine and errors on the attachment slot. The agent group has # no volume, but strategy is app-wide. + # --ha=false because Fly provisions TWO machines by default for + # a newly-declared process group. For `agent` that is actively + # wrong: two workers drain the same run queue, and POST /start + # is idempotent rather than exclusive, so both would claim the + # same run and we would pay for the LLM call twice. The first + # deploy of the agent group did exactly this. echo "Releasing $IMAGE to sentinel-command" - flyctl deploy --image "$IMAGE" --strategy immediate --yes -a sentinel-command + flyctl deploy --image "$IMAGE" --strategy immediate --ha=false --yes -a sentinel-command echo "--- process groups after release ---" flyctl machines list -a sentinel-command --json \ diff --git a/backend/app/sentinel_agent/config.py b/backend/app/sentinel_agent/config.py index e93f943..1f47e40 100644 --- a/backend/app/sentinel_agent/config.py +++ b/backend/app/sentinel_agent/config.py @@ -12,7 +12,7 @@ the MCP server which org each tool call is on behalf of. """ -from pydantic import field_validator, model_validator +from pydantic import AliasChoices, Field, field_validator, model_validator from pydantic_settings import BaseSettings @@ -80,7 +80,25 @@ class Settings(BaseSettings): # The override header is still sent and is accepted as long as it # names the key's own org. opensentry_mcp_url: str = "" # derived from opensentry_api_base if blank - opensentry_mcp_agent_key: str = "" + # Reads OPENSENTRY_MCP_AGENT_KEY first, then Command Center's own + # SENTINEL_AGENT_MCP_KEY. The second alias exists because the agent + # now runs as a process group of the sentinel-command app and shares + # its environment — without it the hosted agent would need a + # duplicate copy of a secret that is already right there, under a + # different name. + # + # This matters more than tidiness: the first-party deployment uses + # two genuinely DIFFERENT shared secrets (run-queue vs MCP), so the + # self-hosted fallback below would quietly hand the MCP surface the + # wrong key — an agent that fetches work and then fails every tool + # call, which is exactly the failure that fallback was written to + # prevent for self-hosters. + opensentry_mcp_agent_key: str = Field( + "", + validation_alias=AliasChoices( + "OPENSENTRY_MCP_AGENT_KEY", "SENTINEL_AGENT_MCP_KEY" + ), + ) # ── Webhook signature behaviour ────────────────────────────────── # When False, /wakeup skips HMAC verification — useful for local