Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Break observer trajectory cycle detection and loop governance ([#93](https://github.com/ARPAHLS/aura/issues/93))** — enhanced `BreakObserver` (`preset: break`) with sliding-window trajectory cycle detection: 1-cycle action fixation ($A \to A \to A$), 2-cycle alternating state oscillation ($A \leftrightarrow B \leftrightarrow A \leftrightarrow B$), 3-cycle execution choreography ($A \to B \to C \to A \to B \to C$), error status fingerprint churn (consecutive identical error codes/messages), and reasoning inertia text similarity sensor (>85% SequenceMatcher ratio across consecutive turn thoughts); FinOps loop burn-velocity guard (`burn_velocity_guard`, `max_cycle_token_waste`) tracking wasted token churn during runaway cycles; non-repudiable cryptographic proof hashes (`cycle_entry_hash`, `cycle_recurrence_hash`) captured from the immutable SHA-256 audit spine conforming to EU AI Act Article 14 human oversight standards; actionable `remedial_directive` steering strings in `observer.alert` payloads; full integration with `profile.escalations[]` (`on: observer.alert`); audit report scorecard `scorecard.observers = {alerts, loop_cycles}` and finding `COGNITIVE_CYCLE_DETECTED` (`severity: warn`); optional Tier 2 `mode: "watchdog"` with pluggable callback; dedicated stress simulation `scripts/aura_break_loop_stress_sim.py` and test suite `tests/test_observer_break_loop.py`.
- **Sequencer middleware and PII masking ([#58](https://github.com/ARPAHLS/aura/issues/58))** — two-tier PII sanitization engine: Tier 1 zero-dependency regex with algorithmic Luhn validation for credit cards, RFC emails, E.164/NANP phones, SSNs, IPv4/IPv6, API keys/JWTs/UUIDs, and IBAN; Tier 2 pluggable `PiiMaskerProvider` protocol for contextual NER models (Presidio, Skillware, ARPA `micro-f1-mask` 270M); masking strategies (`redact`, `mask`, `pseudonymize` / `tokenize`) with deterministic session-scoped pseudonym tokens preserving entity equivalence across multi-step agent reasoning; integration via `MiddlewarePolicy` / `MiddlewareStack` at step boundaries (`scope: per_step`, inbound/outbound) and explicit pipeline step op (`type: op, ref: middleware.pii_mask`); `strict: true` raises `ConstraintViolation` on detection; spine event `middleware.applied` captures detection metrics with zero raw PII written to spine; audit report scorecard `scorecard.middleware = {applied, pii_redactions}` and `PII_MASKED` finding (`severity: info`); dedicated stress simulation `scripts/aura_pii_stress_sim.py` and test suite `tests/test_sequencer_pii_mask.py`.
- **Capability inject for multiple secrets and HTTP headers ([#86](https://github.com/ARPAHLS/aura/issues/86))** — capability declarations support multi-target secret injection via `inject: [{ref, as, prefix}]` alongside single-secret `secret: {ref, as}` backward compatibility; supports nested dot-paths into `args` (e.g. `aws_access_key_id`) and `headers` (e.g. `headers.Authorization`); optional value `prefix` (e.g. `"Bearer "`); `ToolHost.execute` protocol and `SkillwareHost.execute` forward `headers` to executor callables; agent spoofed header keys stripped at egress; all injected secret values registered for leak scrubbing on `tool.result`; `strict_args` reserves all injected target paths; `InjectTarget` exported from `aura` and `aura.core.capabilities`; CLI parity; stress sim scenarios `capability_multi_secret_dual` and `capability_header_authorization` (18 scenarios total).
- **Egress schema and constitution checks ([#78](https://github.com/ARPAHLS/aura/issues/78))** — profile `variables.schema_refs` (named JSON Schema specs per tool) and explicit `schema_check` constraint rules validate `tool.call` args and/or `tool.result` payloads at egress; spectrum `high` / `full` auto-wires refs when `schema_enforcement` is not false; audit finding `SCHEMA_VIOLATION`; conformance `schema` check on close; `session.open` and `aura agent show` spectrum summary include `schema` block; `merged_profile_rules()` helper for session/CLI parity; stress sim `schema_high_bind` scenario; depends on `jsonschema`.
Expand Down
13 changes: 12 additions & 1 deletion aura/agents/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ def _capabilities_from_dict(raw: Any) -> list[dict[str, Any]]:
return capabilities_to_public(caps)


def _sanitize_for_json(obj: Any) -> Any:
"""Recursively convert un-serializable callables into safe descriptors."""
if callable(obj):
return f"<callable {getattr(obj, '__name__', 'unnamed')}>"
if isinstance(obj, dict):
return {k: _sanitize_for_json(v) for k, v in obj.items()}
if isinstance(obj, list):
return [_sanitize_for_json(v) for v in obj]
return obj


@dataclass
class AgentProfile:
"""Persistent agent record."""
Expand Down Expand Up @@ -48,7 +59,7 @@ def to_dict(self) -> dict[str, Any]:
"rules": self.rules,
"skills": self.skills,
"sequencer": self.sequencer,
"observers": self.observers,
"observers": _sanitize_for_json(self.observers),
"escalations": self.escalations,
"spectrum": self.spectrum,
"capabilities": self.capabilities,
Expand Down
29 changes: 29 additions & 0 deletions aura/core/audit_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ def build(
"goal_drifts": sum(1 for e in events if e.kind == "conformance.drift"),
"schedule_misses": sum(1 for e in events if e.kind == "slo.missed"),
},
"observers": {
"alerts": sum(1 for e in events if e.kind == "observer.alert"),
"loop_cycles": sum(
1
for e in events
if e.kind == "observer.alert" and (e.payload or {}).get("pattern")
),
},
"events": conformance.event_count,
}

Expand Down Expand Up @@ -275,6 +283,27 @@ def build(
"event_id": event.event_id,
}
)
if event.kind == "observer.alert":
payload = event.payload or {}
pattern = payload.get("pattern")
if pattern:
findings.append(
{
"severity": "warn",
"code": "COGNITIVE_CYCLE_DETECTED",
"message": payload.get("remedial_directive")
or f"Observer loop cycle detected: {pattern}",
"tool": payload.get("tool"),
"pattern": pattern,
"signature": payload.get("signature"),
"cycle_entry_hash": payload.get("cycle_entry_hash"),
"cycle_recurrence_hash": payload.get("cycle_recurrence_hash"),
"event_id": event.event_id,
}
)
directive = payload.get("remedial_directive")
if directive and directive not in recommendations:
recommendations.append(directive)

open_evt = next((e for e in events if e.kind == "session.open"), None)
spectrum = (open_evt.payload or {}).get("spectrum") if open_evt else None
Expand Down
Loading
Loading