Skip to content
Draft
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
9 changes: 8 additions & 1 deletion containers/agent-pod/healthz.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
GET /activity → POST-only sentinel: bumps /workspace/.last-activity
so the idle watcher resets.
"""

from __future__ import annotations
import http.server, os, shutil, socketserver, time
import http.server
import os
import shutil
import socketserver
import time

LAST_ACTIVITY = "/workspace/.last-activity"


class Handler(http.server.BaseHTTPRequestHandler):
def _ok(self, body=b"ok"):
self.send_response(200)
Expand All @@ -35,6 +41,7 @@ def do_POST(self):
def log_message(self, *args, **kwargs):
pass # quiet probes


if __name__ == "__main__":
port = int(os.environ.get("HEALTHZ_PORT", "8081"))
with socketserver.TCPServer(("0.0.0.0", port), Handler) as srv:
Expand Down
16 changes: 12 additions & 4 deletions containers/model-gateway/refresh-aad-token.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,32 @@
azure-workload-identity webhook), exchanges it for a Cognitive Services
access token, and writes it to /etc/aad/token plus the AZURE_AD_TOKEN env file.
"""

from __future__ import annotations
import os, time, pathlib, sys
import time
import pathlib
import sys
from azure.identity import DefaultAzureCredential

SCOPE = "https://cognitiveservices.azure.com/.default"
OUT_DIR = pathlib.Path("/etc/aad"); OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT_DIR = pathlib.Path("/etc/aad")
OUT_DIR.mkdir(parents=True, exist_ok=True)
TOKEN_FILE = OUT_DIR / "token"
ENV_FILE = OUT_DIR / "env"
ENV_FILE = OUT_DIR / "env"


def refresh() -> int:
cred = DefaultAzureCredential()
tok = cred.get_token(SCOPE)
TOKEN_FILE.write_text(tok.token)
ENV_FILE.write_text(f"AZURE_AD_TOKEN={tok.token}\n")
expires_in = max(60, tok.expires_on - int(time.time()) - 300) # refresh 5 min early
print(f"[refresh-aad] token len={len(tok.token)} expires_in={expires_in}s", flush=True)
print(
f"[refresh-aad] token len={len(tok.token)} expires_in={expires_in}s", flush=True
)
return expires_in


if __name__ == "__main__":
while True:
try:
Expand Down
11 changes: 8 additions & 3 deletions examples/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ async def main() -> None:
foundry = os.getenv("CLAUDE_CODE_USE_FOUNDRY") == "1"
print(
"▶ Claude backend: "
+ (f"Microsoft Foundry ({os.getenv('ANTHROPIC_FOUNDRY_RESOURCE','?')})"
if foundry else "Anthropic public API")
+ (
f"Microsoft Foundry ({os.getenv('ANTHROPIC_FOUNDRY_RESOURCE','?')})"
if foundry
else "Anthropic public API"
)
)
print("▶ Provider routing:")
for role, provider in routing.items():
Expand All @@ -98,7 +101,9 @@ async def main() -> None:
exec_id = getattr(event, "executor_id", "")
# Only treat as error if the attribute is actually set data,
# not the inherited WorkflowEvent.error class method.
err_attr = event.__dict__.get("error") or event.__dict__.get("exception")
err_attr = event.__dict__.get("error") or event.__dict__.get(
"exception"
)
if err_attr is not None:
line = f"{kind}: {exec_id} ERROR={err_attr!r}"
print(f"!! {line}")
Expand Down
1 change: 1 addition & 0 deletions src/code_forge/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""Code Forge — Microsoft Agent Framework graph workflow."""

__version__ = "0.1.0"
12 changes: 9 additions & 3 deletions src/code_forge/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ def _make_openai_agent(client: Any, name: str, instructions: str) -> BaseAgent:
return client.as_agent(name=name, instructions=instructions)


def _make_claude_agent(name: str, instructions: str, sandbox_root: Path | None = None) -> BaseAgent:
def _make_claude_agent(
name: str, instructions: str, sandbox_root: Path | None = None
) -> BaseAgent:
"""Build a ClaudeAgent backed by the Claude Code CLI / Claude Agent SDK.

Two production-grade behaviours wired here:
Expand Down Expand Up @@ -226,7 +228,9 @@ def _resolve_routing() -> dict[str, str]:
return routing


def build_agents(sandbox_root: Path | None = None) -> tuple[dict[str, BaseAgent], dict[str, str]]:
def build_agents(
sandbox_root: Path | None = None,
) -> tuple[dict[str, BaseAgent], dict[str, str]]:
"""Instantiate the five role agents and return (agents, routing).

When ``sandbox_root`` is provided, every Claude-backed agent gets its own
Expand All @@ -244,7 +248,9 @@ def build_agents(sandbox_root: Path | None = None) -> tuple[dict[str, BaseAgent]
provider = routing[role]
display_name = role.replace("_", " ").title().replace(" ", "")
if provider == "claude":
agents[role] = _make_claude_agent(display_name, instructions, sandbox_root=sandbox_root)
agents[role] = _make_claude_agent(
display_name, instructions, sandbox_root=sandbox_root
)
else:
assert openai_client is not None
agents[role] = _make_openai_agent(openai_client, display_name, instructions)
Expand Down
30 changes: 12 additions & 18 deletions src/code_forge/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Any
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from agent_framework import (
AgentExecutorRequest,
Expand All @@ -62,6 +62,11 @@

from .agents import build_agents

if TYPE_CHECKING:
from pathlib import Path

from agent_framework import BaseAgent

# ---------------------------------------------------------------------------
# Message types flowing through the graph
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -130,9 +135,7 @@ def _strip_code_fence(text: str) -> str:

def _parse_verdict(text: str) -> str:
"""Find VERDICT: APPROVED|CHANGES_REQUESTED in the security report."""
m = re.search(
r"VERDICT\s*:\s*(APPROVED|CHANGES_REQUESTED)", text, re.IGNORECASE
)
m = re.search(r"VERDICT\s*:\s*(APPROVED|CHANGES_REQUESTED)", text, re.IGNORECASE)
return m.group(1).upper() if m else "CHANGES_REQUESTED"


Expand Down Expand Up @@ -192,9 +195,7 @@ async def run(

# One outbound message per branch — fan-out via multiple edges.
await ctx.send_message(
_as_request(
"Write pytest tests for the following.\n\n" + package
),
_as_request("Write pytest tests for the following.\n\n" + package),
target_id="test_writer",
)
await ctx.send_message(
Expand All @@ -206,9 +207,7 @@ async def run(
target_id="security_reviewer",
)
await ctx.send_message(
_as_request(
"Write a README section for the following.\n\n" + package
),
_as_request("Write a README section for the following.\n\n" + package),
target_id="doc_writer",
)

Expand Down Expand Up @@ -367,8 +366,7 @@ def build_workflow(
isolated sub-directory under it (one per role) plus bash sandboxing
so concurrent file/bash operations can't collide.
"""
from agent_framework import BaseAgent # noqa: F401 (for the type hint above)
from pathlib import Path # noqa: F401 (forward-ref above)
from agent_framework import AgentExecutor

agents, routing = build_agents(sandbox_root=sandbox_root)

Expand All @@ -383,8 +381,6 @@ def build_workflow(
finalize = Finalize(id="finalize")

# Wrap agents with stable ids that match the target_ids used in fanout.
from agent_framework import AgentExecutor

spec_analyst = AgentExecutor(agents["spec_analyst"], id="spec_analyst")
implementer = AgentExecutor(agents["implementer"], id="implementer")
test_writer = AgentExecutor(agents["test_writer"], id="test_writer")
Expand Down Expand Up @@ -413,9 +409,7 @@ def build_workflow(
builder.add_edge(test_writer, tagged_tests)
builder.add_edge(security_reviewer, tagged_security)
builder.add_edge(doc_writer, tagged_docs)
builder.add_fan_in_edges(
[tagged_tests, tagged_security, tagged_docs], aggregator
)
builder.add_fan_in_edges([tagged_tests, tagged_security, tagged_docs], aggregator)

# Switch-case on verdict + revision count.
def _changes_and_under_cap(r: AggregatedReview) -> bool:
Expand Down
Loading