Skip to content

Add warm agent pool example#189

Open
kaisoz wants to merge 13 commits into
google:mainfrom
kaisoz:warm-agent-pool-example
Open

Add warm agent pool example#189
kaisoz wants to merge 13 commits into
google:mainfrom
kaisoz:warm-agent-pool-example

Conversation

@kaisoz

@kaisoz kaisoz commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

kaisoz added 13 commits July 7, 2026 17:29
Add a pool-manager mcp service that discovers code-reviewer workers via DHT
(find_remote_tools) and leases them out through acquire_worker/release_worker,
with fencing tokens and grace-eviction so a busy worker is never freed early.
Add a single-flight backstop to the reviewer, a headless orchestrator plus
sample files, and wire node-b/c/d as a reviewer pool with node-e as the manager
in the kind dev mesh.
…de (:9099)

Make the local sam-node (make kind-local-node) the base orchestrator entry:
default SAM_NODE_URL to http://127.0.0.1:9099/mcp and document make kind-up +
make kind-local-node instead of port-forwarding node-a.
Create a fresh McpServer + transport per POST; the SDK forbids connecting
one server to multiple transports.
Derive kind image/container name from the service basename so the nested
service path stays a valid k8s container name.
… samples

The headless orchestrator method is going away. Fold the run guide (Claude
Code-driven flow) and lease-enforcement docs into code-reviewer-pool/README.md
and move the sample files alongside the services.
Drop the SAM_POOL_SECRET opt-in guards so the manager always mints a token and
the reviewer always requires one. Both sides default to a shared hardcoded dev
secret (sam-dev-pool-secret) so kind enforces out of the box with no env wiring;
SAM_POOL_SECRET still overrides it.
…kind mesh to bare nodes

Add a harness-agnostic "Use Cases" docs section (site/content/docs/use-cases/)
whose first entry explains the warm agent pool and how to try it on kind.

Make the kind mesh-config.yaml ship all five nodes bare so the user decides what
to host. Since the e2e check needs calc-mcp, add an overridable MESH_CONFIG in
run.sh and a mesh-config.e2e.yaml that pins it; the CI kind-up step selects it.
Update the Kubernetes/kind local-testing guide to match (five bare nodes, the
MESH_CONFIG override flow).
Remove the standalone docker-compose path (docker-compose.yaml, env.example,
and the compose-only sam-node.yaml) and the README. The example is meant to run
only inside the mesh/kind sandbox, which uses sam-node-config.yaml.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'Warm Agent Pool' architecture under development/examples/code-reviewer-pool/, replacing the single code-reviewer server with a manager-worker model that leases workers using signed HMAC tokens. It also expands the local Kubernetes development mesh from three to five nodes and updates the documentation. The reviewer feedback highlights several robustness improvements: validating tool call responses before parsing them, adding error listeners to child.stdin when spawning subprocesses to prevent unhandled crashes, and wrapping asynchronous server initialization in a try-catch block to handle startup failures gracefully.

Comment on lines +34 to +35
const res = await node.callTool({ name: "find_remote_tools", arguments: {} });
const rows = JSON.parse(res.content[0].text); // [{peer_id, tool_name, ...}]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing res.content[0].text directly without checking if res.content is defined and non-empty can lead to a TypeError if the tool call returns no content or fails. It is safer to validate the presence of res.content before parsing.

    const res = await node.callTool({ name: "find_remote_tools", arguments: {} });
    if (!res.content || res.content.length === 0) {
      throw new Error("No content returned from find_remote_tools");
    }
    const rows = JSON.parse(res.content[0].text); // [{peer_id, tool_name, ...}]

let stderr = "";
child.stdout.on("data", (d) => { stdout += d; });
child.stderr.on("data", (d) => { stderr += d; });
child.on("error", reject);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When spawning a child process and writing to its stdin, an unhandled EPIPE or ERR_STREAM_WRITE_AFTER_END error can occur if the process fails to start or exits prematurely. Adding an error listener to child.stdin prevents potential unhandled crashes.

    child.on("error", reject);
    child.stdin.on("error", reject);

Comment on lines +138 to +146
app.listen(PORT, "0.0.0.0", async () => {
console.log(`pool-manager MCP server on :${PORT}/mcp (pooling '${POOL_SERVICE}')`);
const transport = new StreamableHTTPClientTransport(new URL(NODE_URL), {
requestInit: { headers: { Authorization: `Bearer ${API_TOKEN}` } },
});
await node.connect(transport);
await discover();
setInterval(discover, DISCOVERY_MS);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The asynchronous initialization logic inside app.listen is not wrapped in a try-catch block. If node.connect or discover fails during startup, it will result in an unhandled promise rejection, which can leave the server in a semi-broken state or crash the process. Wrapping this in a try-catch block ensures the error is handled gracefully.

Suggested change
app.listen(PORT, "0.0.0.0", async () => {
console.log(`pool-manager MCP server on :${PORT}/mcp (pooling '${POOL_SERVICE}')`);
const transport = new StreamableHTTPClientTransport(new URL(NODE_URL), {
requestInit: { headers: { Authorization: `Bearer ${API_TOKEN}` } },
});
await node.connect(transport);
await discover();
setInterval(discover, DISCOVERY_MS);
});
app.listen(PORT, "0.0.0.0", async () => {
console.log("pool-manager MCP server on :" + PORT + "/mcp (pooling '" + POOL_SERVICE + "')");
try {
const transport = new StreamableHTTPClientTransport(new URL(NODE_URL), {
requestInit: { headers: { Authorization: `Bearer ${API_TOKEN}` } },
});
await node.connect(transport);
await discover();
setInterval(discover, DISCOVERY_MS);
} catch (err) {
console.error("Failed to initialize MCP client connection: " + (err?.message ?? err));
}
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant