Feature: Code execution Middleware - #116
Draft
saharannaveen wants to merge 30 commits into
Draft
Conversation
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
Feat/rhitaif 221
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.
… designed output channel
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.
…e for NetworkPolicy
- 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
marked this pull request as draft
July 20, 2026 13:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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:
created inside try block and cleaned up in finally.
in deny mode).