The problem with agents and microservices
How Signadot works?
Prerequisites
- A Signadot account (free tier available)
- Signadot CLI v1.4.0+
- A Kubernetes cluster (minikube, k3s, EKS, GKE, AKS — anything works)
- Go 1.21+ (for running the demo service locally)
- A coding agent with MCP support (Cursor, Claude Code, or VS Code)
Install the Signadot CLI
# macOS
brew install signadot/tap/signadot-cli
# Linux
curl -sSLf https://raw.githubusercontent.com/signadot/cli/main/scripts/install.sh | sh
# Verify
signadot version
Install the Demo Application
kubectl create ns hotrod --dry-run=client -o yaml | kubectl apply -f -
# If using Istio:
kubectl -n hotrod apply -k 'https://github.com/signadot/hotrod/k8s/overlays/prod/istio'
# If using Linkerd or no service mesh:
kubectl -n hotrod apply -k 'https://github.com/signadot/hotrod/k8s/overlays/prod/linkerd'
Set Up the MCP Server
signadot mcp is the command you'd want to set up in your editor of choice.Part 1: Connecting to the Cluster
Authenticate
signadot auth login
Configure the Connection
~/.signadot/config.yaml:local:
connections:
- cluster: <your-cluster-name>
type: ControlPlaneProxy
<your-cluster-name> with your cluster from app.signadot.com/settings/clusters.Connect
signadot local connect
/etc/hosts with cluster service names and configures networking. Verify it works:curl http://frontend.hotrod.svc:8080
Part 2: Adding a Feature with a Coding Agent
route service and validate it against live cluster dependencies.Clone the Repo
git clone https://github.com/signadot/hotrod.git
cd hotrod
Create a Local Sandbox
route service locally, my local version needs to be reachable from — and able to reach — every other service in the cluster. Traditionally that means running everything locally (painful) or pushing a new image and waiting for a rollout (slow, and shared with the whole team).route service to a process on my laptop, but only for requests carrying my routing key. Everyone else's traffic is untouched.# Save as local-route.yaml
cat << 'EOF' > local-route.yaml
name: local-route-dev
spec:
description: Local development for route service
cluster: "<your-cluster-name>"
ttl:
duration: 1d
local:
- name: "local-route"
from:
kind: Deployment
namespace: hotrod
name: route
mappings:
- port: 8083
toLocal: "localhost:8083"
EOF
signadot sandbox apply -f ./local-route.yaml
Prompt
Start the Service Locally
eval $(signadot sandbox get-env local-route-dev)
go run ./cmd/hotrod/main.go route
route service is now running on your machine, connected to real cluster dependencies.How the routing works
route pod in the cluster? It uses the OpenTelemetry baggage header with a key called sd-routing-key. When you activate a sandbox, either with the Chrome extension or by setting the header yourself, that key is attached to every request. Signadot's routing layer in the cluster reads it, matches it to your sandbox, and forwards the request to your local process. No matching key means the request stays on the baseline pod.Build the Fare Feature
Prompt
local-route-dev, add a /fare endpoint to the route service. It should accept pickup and dropoff coordinates as query parameters, use the existing route calculation to get the ETA, and return an estimated fare as JSON. Once it's implemented, test it against the live sandbox.curl -s "http://route.hotrod.svc:8083/fare?pickup=231,773&dropoff=115,277" | jq
{
"pickup": "231,773",
"dropoff": "115,277",
"eta": 70,
"fare": "$38.00"
}
Testing a Change Across Multiple Services
- route — calculates the fare and returns it as part of the FindRoute response (with a matching proto/interface change to add a cost field).
- driver — passes the fare through and sends it to the frontend as part of the dispatch notification.
- frontend — displays the fare it parses from that notification.
# Save as fare-feature.yaml
cat << 'EOF' > fare-feature.yaml
name: fare-feature-dev
spec:
description: Local dev for the fare feature across route, driver, and frontend
cluster: "<your-cluster-name>"
ttl:
duration: 1d
local:
- name: local-route
from:
kind: Deployment
namespace: hotrod
name: route
mappings:
- port: 8083
toLocal: "localhost:8083"
- name: local-driver
from:
kind: Deployment
namespace: hotrod
name: driver
mappings:
- port: 8082
toLocal: "localhost:8082"
- name: local-frontend
from:
kind: Deployment
namespace: hotrod
name: frontend
mappings:
- port: 8080
toLocal: "localhost:8080"
EOF
signadot sandbox apply -f ./fare-feature.yaml
--local flag selects which one — and start the three services, each in its own terminal:# Terminal 1 — route
eval $(signadot sandbox get-env fare-feature-dev --local local-route)
go run ./cmd/hotrod/main.go route
# Terminal 2 — driver
eval $(signadot sandbox get-env fare-feature-dev --local local-driver)
go run ./cmd/hotrod/main.go driver
# Terminal 3 — frontend
eval $(signadot sandbox get-env fare-feature-dev --local local-frontend)
go run ./cmd/hotrod/main.go frontend
fare-feature-dev sandbox in the Chrome extension.fare-feature-dev sandbox in the Chrome extension]Debugging with Traffic Recording and Overrides
Create a Sandbox with the Bug
# Save as pr-263-location.yaml
cat << 'EOF' > pr-263-location.yaml
name: pr-263-location
spec:
description: Location service with breaking field name change
cluster: "<your-cluster-name>"
ttl:
duration: 1d
forks:
- forkOf:
kind: Deployment
namespace: hotrod
name: location
customizations:
images:
- image: signadot/hotrod:ee63f5381680e089fec075e9adb8f4c7c0cda38f-linux-amd64
EOF
signadot sandbox apply -f pr-263-location.yaml
pr-263-location sandbox with the Chrome extension, then visit http://frontend.hotrod.svc:8080 and try to request a ride. You'll notice the location dropdown is broken as there's no names for locations.Recording Traffic
signadot traffic record --sandbox pr-263-location --inspect --clean
GET /locations request in the TUI and look at the response body:[
{
"id": 1,
"locName": "Dog My Home",
"coordinates": "231,773"
}
]
locName, but the frontend expects name. You can press Ctrl+C to stop recording.Testing a fix for the issue
/locations endpoint and returns the correct field name. Note that this could be in any programming language, it doesn't really matter, as long as it can handle HTTP requests.location-fix.js:const express = require('express');
const app = express();
app.use(express.json());
const LOCATIONS_DATA = [
{ id: 1, name: "Dog My Home", coordinates: "231,773" },
{ id: 123, name: "Lion Rachel's Floral Designs", coordinates: "115,277" },
{ id: 392, name: "Bear Trom Chocolatier", coordinates: "577,322" },
{ id: 567, name: "Eagle Amazing Coffee Roasters", coordinates: "211,653" },
{ id: 731, name: "Tiger Japanese Desserts", coordinates: "728,326" }
];
// Override this one endpoint
app.get('/locations', (req, res) => {
res.set('sd-override', 'true');
res.json(LOCATIONS_DATA);
});
// Let everything else fall through to the sandbox baseline
app.use((req, res) => res.status(404).end());
const port = process.env.PORT || 8081;
app.listen(port, () => console.log(`Local override server running on port ${port}`));
sd-override: true response header in the /locations endpoint. When Signadot sees that value, it returns the local response to the caller. When it's absent, the request falls through to the sandbox. That gives you surgical precision - override only the exact endpoint you're fixing, and everything else in the sandbox runs normally.npm install express
node location-fix.js
Apply the Override
signadot local override \
--sandbox pr-263-location \
--workload location \
--workload-port 8081 \
--with localhost:8081
pr-263-location sandbox still active. The location dropdown works again! In this example we went full circle - we created an isolated sandbox, recorded traffic to diagnose an issue, implemented a local override to fix it and then validated to confirm the fix. This took only a couple of minutes, and you never touched the shared environment.





