Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/agent-orchestrator/src/agent/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,25 @@ describe("buildAgentGraph", () => {
expect(final.error).toMatch(/tool failed \(extraction\)/);
});

it("surfaces a launch failure (e.g. the k8s API call itself failing) as a labeled graph error instead of an uncaught, contextless exception", async () => {
// Regression test: `containerToolLauncher.launch()` throwing (rather than
// resolving and the Job later reporting a `failed` Event) used to escape
// every catch in the graph and reach the SSE layer as a bare
// `err.message` -- e.g. "fetch failed" from the Kubernetes client, with
// no indication which tool was being launched or that launch itself (not
// the tool) was what failed.
const deps = baseDeps({
containerToolLauncher: {
launch: vi.fn().mockRejectedValue(new TypeError("fetch failed")),
} as unknown as ContainerToolLauncher,
});
const graph = buildAgentGraph(deps);

const final = await graph.invoke({ request: "do a thing", authToken: "tok" });

expect(final.error).toMatch(/tool recipe-scraper failed to launch: fetch failed/);
});

it("runs a LocalTool via the executor sidecar instead of launching a Job (ADR 0014)", async () => {
const localTool: ToolDescriptor = {
id: "http-get-node",
Expand Down
15 changes: 15 additions & 0 deletions apps/agent-orchestrator/src/agent/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,21 @@ export function buildAgentGraph(deps: AgentGraphDeps) {
}

event = await awaitResult;
} catch (err) {
// Unlike the agent-backed branch above, nothing here has yet
// produced a structured `failed` Event -- `launch()` can throw
// before the Job/ToolRun even exists (e.g. the k8s API call itself
// failing), and `awaitResult` can reject for reasons outside the
// tool's own control. Both used to propagate uncaught out of this
// node, past every catch in the graph, and surface at the SSE layer
// as a bare `err.message` -- e.g. "fetch failed" from the
// Kubernetes client with no indication which tool, which call, or
// that a ToolRun was never even created. Catching and labeling here
// keeps that context.
return {
jobId,
error: `tool ${tool.id} failed to launch: ${err instanceof Error ? err.message : String(err)}`,
};
} finally {
unsubscribeProgress();
}
Expand Down
22 changes: 22 additions & 0 deletions charts/agent-controller/values-production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ agent-orchestrator:
gatewayUrl: "http://agent-controller-integration-gateway:8090"

config:
# This cluster has no Temporal deployment at all (see temporal-engine.enabled
# below) -- clear the umbrella chart's own default of "temporal"
# (charts/agent-controller/values.yaml, docs/adr/0036's rollout default) so
# every turn runs the in-process LangGraph loop instead of being forwarded
# to a gateway Service backed by pods that were never buildable here.
# Helm deep-merges this `config` map key-by-key against that default, so
# omitting this key (rather than setting it) would silently inherit
# "temporal" -- which is exactly what had been happening.
agentEngine: ""
temporalEngineUrl: ""
# oidc against Pocket ID (kubernetes/manifests/services/pocket-id in
# imaustink/homelab), not Google -- the 2026-07-19 outage was caused by
# Google id_tokens: neither caller could present one (Open WebUI has no
Expand Down Expand Up @@ -194,6 +204,18 @@ agent-orchestrator:
redis:
enabled: true

# No Temporal cluster exists in this environment (docs/adr/0036 "Assumes a
# reachable Temporal cluster; no server is bundled") -- override the umbrella
# chart's own default of enabled: true (charts/agent-controller/values.yaml)
# so this release stops deploying the gateway/worker Deployments here. Without
# this they run permanently in ImagePullBackOff (their image names have no
# registry prefix, so containerd resolves them against Docker Hub, which has
# never had them) while agent-orchestrator.config.agentEngine above still
# forwarded every turn to the (nonexistent) gateway -- the underlying cause of
# turns failing with a bare "fetch failed".
temporal-engine:
enabled: false

core-controller:
enabled: true
fullnameOverride: core-controller
Expand Down
Loading