Skip to content

Commit 49bf9cd

Browse files
[Contrib] Agent-OS Integration: Kernel-Level Safety for RL Training (#478)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 9864b8f commit 49bf9cd

8 files changed

Lines changed: 846 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
# Namespace package for agentlightning.contrib.adapter.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""
4+
FlightRecorderAdapter - Import Audit Logs to LightningStore
5+
=============================================================
6+
7+
Adapts Agent-OS Flight Recorder to Agent-Lightning store format.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import logging
13+
from datetime import datetime, timezone
14+
from typing import Any, Dict, List
15+
16+
logger = logging.getLogger(__name__)
17+
18+
19+
class FlightRecorderAdapter:
20+
"""
21+
Import Agent-OS Flight Recorder logs to LightningStore.
22+
23+
Example:
24+
>>> from agent_os import FlightRecorder
25+
>>>
26+
>>> recorder = FlightRecorder()
27+
>>> adapter = FlightRecorderAdapter(recorder)
28+
>>>
29+
>>> # Import to Lightning store
30+
>>> adapter.import_to_store(lightning_store)
31+
"""
32+
33+
def __init__(
34+
self,
35+
flight_recorder: Any,
36+
*,
37+
trace_id_prefix: str = "agentos",
38+
):
39+
"""
40+
Initialize adapter.
41+
42+
Args:
43+
flight_recorder: Agent-OS FlightRecorder
44+
trace_id_prefix: Prefix for trace IDs
45+
"""
46+
self.recorder = flight_recorder
47+
self.trace_id_prefix = trace_id_prefix
48+
self._imported_count = 0
49+
50+
def _convert_entry(self, entry: Any, index: int) -> Dict[str, Any]:
51+
"""Convert Flight Recorder entry to span format."""
52+
entry_type = getattr(entry, "type", "unknown")
53+
timestamp = getattr(entry, "timestamp", datetime.now(timezone.utc))
54+
agent_id = getattr(entry, "agent_id", "unknown")
55+
56+
span = {
57+
"span_id": f"{self.trace_id_prefix}-{index}",
58+
"trace_id": f"{self.trace_id_prefix}-{agent_id}",
59+
"name": f"agent_os.{entry_type}",
60+
"start_time": timestamp.isoformat() if hasattr(timestamp, "isoformat") else str(timestamp),
61+
"attributes": {
62+
"agent_os.entry_type": entry_type,
63+
"agent_os.agent_id": agent_id,
64+
},
65+
}
66+
67+
# Add type-specific attributes
68+
if entry_type == "policy_check":
69+
span["attributes"].update(
70+
{
71+
"agent_os.policy_name": getattr(entry, "policy_name", "unknown"),
72+
"agent_os.policy_violated": getattr(entry, "violated", False),
73+
}
74+
)
75+
elif entry_type == "signal":
76+
span["attributes"].update(
77+
{
78+
"agent_os.signal_type": getattr(entry, "signal", "unknown"),
79+
}
80+
)
81+
82+
return span
83+
84+
def get_spans(self) -> List[Dict[str, Any]]:
85+
"""Get all entries as spans."""
86+
entries = []
87+
if hasattr(self.recorder, "get_entries"):
88+
entries = self.recorder.get_entries()
89+
elif hasattr(self.recorder, "entries"):
90+
entries = self.recorder.entries
91+
92+
return [self._convert_entry(e, i) for i, e in enumerate(entries)]
93+
94+
def import_to_store(self, store: Any) -> int:
95+
"""
96+
Import spans to LightningStore.
97+
98+
Args:
99+
store: LightningStore instance
100+
101+
Returns:
102+
Number of spans imported
103+
"""
104+
spans = self.get_spans()
105+
106+
for span in spans:
107+
try:
108+
if hasattr(store, "emit_span"):
109+
store.emit_span(span)
110+
elif hasattr(store, "add_span"):
111+
store.add_span(span)
112+
except Exception as e:
113+
logger.error(f"Failed to import span: {e}")
114+
115+
self._imported_count += len(spans)
116+
logger.info(f"Imported {len(spans)} spans to LightningStore")
117+
return len(spans)
118+
119+
def get_violation_summary(self) -> Dict[str, Any]:
120+
"""Get summary of policy violations."""
121+
spans = self.get_spans()
122+
violations = [s for s in spans if s["attributes"].get("agent_os.policy_violated", False)]
123+
return {
124+
"total_entries": len(spans),
125+
"total_violations": len(violations),
126+
"violation_rate": len(violations) / len(spans) if len(spans) > 0 else 0.0,
127+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
# Namespace package for agentlightning.contrib.reward.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""
4+
PolicyReward - Convert Policy Violations to RL Penalties
5+
=========================================================
6+
7+
Reward function that integrates Agent-OS governance.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import logging
13+
from typing import Any, Callable, Dict, Optional
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class PolicyReward:
19+
"""
20+
Reward function that penalizes policy violations.
21+
22+
Example:
23+
>>> from agent_os import KernelSpace
24+
>>>
25+
>>> kernel = KernelSpace(policy="strict")
26+
>>> reward_fn = PolicyReward(kernel, base_reward_fn=accuracy)
27+
>>>
28+
>>> reward = reward_fn(rollout) # Base reward - violation penalties
29+
"""
30+
31+
def __init__(
32+
self,
33+
kernel: Any,
34+
*,
35+
base_reward_fn: Optional[Callable[[Any], float]] = None,
36+
critical_penalty: float = -100.0,
37+
high_penalty: float = -50.0,
38+
medium_penalty: float = -10.0,
39+
low_penalty: float = -1.0,
40+
clean_bonus: float = 5.0,
41+
):
42+
"""
43+
Initialize policy-aware reward.
44+
45+
Args:
46+
kernel: Agent-OS KernelSpace
47+
base_reward_fn: Base reward function
48+
critical_penalty: Penalty for critical violations
49+
high_penalty: Penalty for high violations
50+
medium_penalty: Penalty for medium violations
51+
low_penalty: Penalty for low violations
52+
clean_bonus: Bonus for clean execution
53+
"""
54+
self.kernel = kernel
55+
self.base_reward_fn = base_reward_fn or self._default_reward
56+
self.penalties = {
57+
"critical": critical_penalty,
58+
"high": high_penalty,
59+
"medium": medium_penalty,
60+
"low": low_penalty,
61+
}
62+
self.clean_bonus = clean_bonus
63+
64+
self._total_rewards = 0
65+
self._total_penalties = 0.0
66+
67+
def _default_reward(self, rollout: Any) -> float:
68+
"""Default: 1.0 for success, 0.0 for failure."""
69+
return 1.0 if getattr(rollout, "success", False) else 0.0
70+
71+
def __call__(self, rollout: Any, *, emit: bool = True) -> float:
72+
"""
73+
Calculate reward with policy penalties.
74+
75+
Args:
76+
rollout: Rollout with violations attribute
77+
emit: Emit reward span
78+
79+
Returns:
80+
Final reward
81+
"""
82+
base = self.base_reward_fn(rollout)
83+
84+
violations = getattr(rollout, "violations", [])
85+
penalty = sum(self.penalties.get(v.severity, -10.0) for v in violations)
86+
87+
reward = base + penalty
88+
if not violations:
89+
reward += self.clean_bonus
90+
91+
self._total_rewards += 1
92+
self._total_penalties += penalty
93+
94+
if emit:
95+
self._emit_reward(reward, base, penalty, len(violations))
96+
97+
return reward
98+
99+
def _emit_reward(
100+
self,
101+
final: float,
102+
base: float,
103+
penalty: float,
104+
violation_count: int,
105+
) -> None:
106+
"""Emit multi-dimensional reward."""
107+
try:
108+
from agentlightning.emitter import emit_reward
109+
110+
emit_reward(
111+
{"final": final, "base": base, "policy_penalty": penalty},
112+
primary_key="final",
113+
attributes={"agent_os.violations": violation_count},
114+
)
115+
except ImportError:
116+
logger.debug(
117+
"agentlightning.emitter not available; skipping reward emission.",
118+
exc_info=True,
119+
)
120+
121+
def get_stats(self) -> Dict[str, float]:
122+
"""Get reward statistics."""
123+
total = self._total_rewards or 1
124+
return {
125+
"total_rewards": self._total_rewards,
126+
"avg_penalty": self._total_penalties / total,
127+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
# Namespace package for agentlightning.contrib.runner.

0 commit comments

Comments
 (0)