Skip to content

Feature: Code execution Middleware - #116

Draft
saharannaveen wants to merge 30 commits into
redhat-data-and-ai:deep-agentfrom
saharannaveen:feat/codexecmiddleware
Draft

Feature: Code execution Middleware#116
saharannaveen wants to merge 30 commits into
redhat-data-and-ai:deep-agentfrom
saharannaveen:feat/codexecmiddleware

Conversation

@saharannaveen

Copy link
Copy Markdown

Description

What does this MR do and why?

Implements CodeExecutionMiddleware — a deepagents AgentMiddleware that injects an execute_code tool into the agent and
routes calls to ephemeral K8s Jobs for sandboxed code execution. Agents can now generate Python/shell/Node code, execute it
in an isolated container, and receive stdout/stderr back — with full observability, security enforcement, and automatic
cleanup.

Changes

Core middleware (deep_agent/src/code_execution/middleware.py): CodeExecutionMiddleware(AgentMiddleware) injects execute_code
tool via awrap_model_call, intercepts calls in awrap_tool_call, validates input (language, code length, empty code, file
size), manages per-org concurrency via asyncio.Semaphore, routes to K8sJobRunner, and returns structured ToolMessage with
stdout/stderr/exit_code.

K8s Job runner (deep_agent/src/code_execution/k8s_job_runner.py): Manages the full ephemeral Job lifecycle — manifest
generation with full pod security context (non-root, read-only FS, no SA token, seccomp, drop all caps), pod polling, log
collection (post-completion + streaming via follow=True), container status parsing (success/failed/timeout/OOM), cost
tracking via K8s Metrics API, NetworkPolicy creation/deletion per execution, ConfigMap-based file I/O, and cleanup in
finally block.

Configuration (deep_agent/src/code_execution/config.py): Pydantic model with configurable images, resource limits, timeout
(5-300s, default 60s), network access control (deny/allow_internet/per_execution), execution queuing (max concurrent per
org, queue timeout), cost tracking, streaming, and file I/O limits.

Observability (deep_agent/src/code_execution/metrics.py): 4-layer observability using stdlib logging (to ensure output
inside LangGraph graph-execution context): structured JSON logs for all lifecycle events, OTEL metric recording, OTEL
tracing spans, and platform audit event emission.

Wiring (deep_agent/src/infrastructure/middleware.py, deep_agent/src/agent/config/middleware.py): Registered in
build_middleware_list() following the existing middleware builder pattern. Config loaded from code_execution: section in
agent.yaml.

SSE streaming: Middleware wires LangGraph StreamWriter callback to K8sJobRunner.on_output for real-time code output
streaming through Aegra → BFF → UI.

Prompt updates (config/agent/PROMPT.md): Added code execution guidance — agent uses execute_code automatically for
computation tasks, with fallback for BMI when analyst subagent is unavailable.

Design spec with Mermaid diagrams covering architecture, before/after, platform integration, request flow, security constraints, K8s Job spec, observability design (4 layers), error handling taxonomy, alternatives analysis, imageconfiguration flow, and ephemeral pod observability guide.

AI Disclosure

AI used: Yes
Tool(s): None
Scope: Full implementation — design spec, middleware, K8s runner, config, metrics, tests, prompt engineering, bug fixes from code review
Human verification: Tested end-to-end on Kind cluster — Python/shell execution, file I/O via ConfigMap, NetworkPolicy
creation/deletion, execution queuing, cost tracking metrics, log streaming. 59 unit tests passing. All pre-commit hooks
green (ruff, mypy, pydocstyle, bandit).

Checklist

  • I have reviewed my own diff
  • Tests pass with adequate coverage (59 unit tests)
  • Docs and config updated (1,854-line design spec, agent.yaml, PROMPT.md)
  • AI output verified for correctness and hallucinated dependencies

Deployment & Security Impact

Deployment: Code execution enabled by default (code_execution.enabled: true). Requires kubernetes Python package (imported lazily — no crash if missing, just runtime error on first execute_code call). Agent pod ServiceAccount needs RBAC Role for batch/jobs (create/get/delete), pods (get/list), pods/log (get), networking.k8s.io/networkpolicies (create/delete) in its namespace. No DB migrations.

Security: Execution pods run with runAsNonRoot, readOnlyRootFilesystem, automountServiceAccountToken: false,
capabilities.drop: [ALL], seccompProfile: RuntimeDefault. NetworkPolicy created per execution to control egress. Code
content never logged — only SHA-256 hash in audit events. Resource limits enforced via K8s resources.limits. Timeout
enforced via activeDeadlineSeconds + client-side asyncio.wait_for.

Reviewer Notes

Focus on:

  • k8s_job_runner.py lines 225-340 — the run() method orchestrating the full lifecycle. Verify ConfigMap/NetworkPolicy
    created inside try block and cleaned up in finally.
  • middleware.py lines 140-260 — semaphore acquire/release flow. Verify acquired flag prevents over-release on timeout.
  • k8s_job_runner.py line 595 — egress=egress (was egress if egress else None which treated [] as falsy → allowed all egress
    in deny mode).
  • middleware.py lines 176-185 — StreamWriter callback. Verify graceful degradation when get_stream_writer() not available.

nsaharan and others added 30 commits July 3, 2026 15:19
Add allowed_tools, denied_tools, and tool_approval config in subagent
frontmatter. Enforce at MCP tool bind time with deny-wins-over-allow
semantics. Compiled subagents get their own interrupt_on for tool_approval.
Default subagents with tool_approval raise a validation error (must use
compiled type). Backward compat: tools field auto-migrates to allowed_tools.

- Rename tools → allowed_tools in frontmatter (with deprecation shim)
- Add denied_tools filtering at subagent build time
- Add tool_approval → interrupt_on for compiled subagents
- Validation: async/default subagents reject tool_approval
- 52 unit tests, isolation and precedence verified
Adds a DynamicToolMiddleware that injects an execute_code tool into the
agent and routes calls to ephemeral K8s Jobs for sandboxed code execution.

Components:
- CodeExecutionConfig: Pydantic model for images, resources, timeouts
- K8sJobRunner: Job lifecycle (create, wait, logs, cleanup) with full
  security context (non-root, read-only FS, no SA token, seccomp)
- CodeExecutionMiddleware: AgentMiddleware with tool injection via
  awrap_model_call and K8s routing via awrap_tool_call
- CodeExecutionMetrics: 4-layer observability (OTEL metrics, tracing,
  audit events, structured logs)

Supports Python, shell, and Node.js with configurable images, resource
limits, and timeouts. Jobs auto-delete via ttlSecondsAfterFinished +
explicit cleanup in finally block.

35 unit tests covering config validation, Job manifest generation,
security fields, language mapping, status parsing, tool injection,
routing, and observability.
…s logs

- parse_container_status: check exit_code=0 for success regardless of
  termination reason (K8s sets reason='Completed' on success)
- _collect_logs: decode bytes responses from K8s pod log API
- _wait_for_pod: only return on Succeeded/Failed (not Running)
- _get_exit_info: fall back to pod phase when container status unavailable
- Add test for exit_code=0 with reason='Completed'

Verified with live K8s Jobs on Kind cluster: 4/4 scenarios pass.
- Switch metrics/k8s_job_runner to stdlib logging with explicit
  StreamHandler(stderr) for reliable output inside LangGraph graph
  execution context where structlog's cached proxy doesn't reach stdout
- Add code execution section to PROMPT.md so the LLM automatically
  uses execute_code for computation tasks without explicit instruction
- Keep code_execution.enabled default as false (opt-in per deployment)
Phase 2 features for CodeExecutionMiddleware:

1. Custom Images — python-ds, python-ml domain variants via config
2. Network Access Control — per-execution NetworkPolicy (deny/allow/per_execution)
3. Execution Queuing — per-org asyncio.Semaphore with concurrency + timeout
4. File I/O — ConfigMap input at /input, emptyDir /output volume
5. Cost Tracking — OTEL metrics for cpu_seconds, memory_mb_seconds
6. Streaming — real-time stdout/stderr via follow=True with callback

59 unit tests covering all features.
…execution

Tell the orchestrator that execute_code is the ONE exception to the
delegation rule — it should call execute_code itself for computation
and visualization, while still delegating domain work to subagents.
1. NetworkPolicy egress=[] treated as falsy → None → allows ALL egress
   instead of blocking. Fixed: pass egress list directly.

2. Semaphore released in finally even when acquire() timed out →
   over-release breaks concurrency limit. Fixed: track acquired flag.

3. NetworkPolicy not created in deny mode → pods get unrestricted
   network by default. Fixed: always create NetworkPolicy.

4. ConfigMap/NetworkPolicy created outside try block → leaked on
   manifest build failure. Fixed: moved inside try/finally.

5. Streaming for-loop blocks event loop synchronously. Fixed: wrapped
   in asyncio.to_thread.

6. Empty code string passes validation. Fixed: reject code.strip()==''.

7. Unused duration variable in exception handler. Fixed: pass to
   log_failed for failed execution timing.
- Wire StreamWriter callback in middleware → runner for real-time
  code execution output via LangGraph custom stream events
- Enable streaming_enabled and code_execution.enabled by default
- Add execute_code to HITL exclude list (no approval needed)
- Fix prompt: remove python-ds/python-ml references, add BMI fallback
  when analyst subagent is unavailable
- Remove placeholder domain images from config defaults
1. OTEL Metrics — 9 instruments on MetricsContainer in otel.py
2. OTEL Tracing — trace_span() with active context for trace_id
3. Audit Events — emitter.py + context.py with sensitive key redaction
4. Scheduling Latency — measured and recorded via OTEL + logs
5. Image + namespace in log events

67 unit tests. Dashboard script at scripts/code-exec-dashboard.sh.
Took deep-agent's audit/* (PR redhat-data-and-ai#79 — more complete with buffer,
config, resolve_trace_id), subagents.py, and PROMPT.md (headless
worker section). Added CODE_EXECUTION to events.py. Inserted code
execution section into merged PROMPT.md.
@saharannaveen
saharannaveen marked this pull request as draft July 20, 2026 13:52
@NP-compete NP-compete added the deep-agent PRs targeting the deep-agent branch label Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-agent PRs targeting the deep-agent branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants