Skip to content
Open
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
8 changes: 8 additions & 0 deletions examples/config/global_controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ agents:
memory: 512 # Memory in MB
# Path to the agent entrypoint script
entrypoint: agents/finance_agent.py
# Output policies run automatically after this agent produces a result.
# Each entry is a `module:function` reference resolved by the local
# controller. Policies are side-effect only (audit / notify / enforce):
# they react to the output but do not modify it, so their order does not
# matter. Decision logic lives inside the policy itself.
output_policies:
- policies.finance:audit_log
- policies.finance:alert_on_sensitive

- name: MarketResearchAgent
host: localhost
Expand Down
61 changes: 61 additions & 0 deletions examples/policies/finance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Example output policies for FinanceAgent.
#
# An output policy is any callable with the signature:
#
# policy(output, ctx) -> None
#
# - `output` is the agent method's return value.
# - `ctx` carries request metadata: at least `request_id`, `service`,
# `function`, plus whatever request context was propagated
# (e.g. `origin`, `tags`).
#
# Policies are SIDE-EFFECT ONLY: they observe/react to the output (audit,
# notify, enforce) but do not modify it. Any return value is ignored, so the
# policies are independent of one another and their order does not affect the
# result. The framework only finds and runs the configured policies; the
# decision of whether/how to act lives inside each policy.
#
# Bind them in the controller config:
#
# agents:
# - name: FinanceAgent
# output_policies:
# - policies.finance:audit_log
# - policies.finance:alert_on_sensitive

import logging

logger = logging.getLogger(__name__)

# Toy list of tokens we never want to leak in a response.
_SENSITIVE = ("SSN", "password", "api_key")


def audit_log(output, ctx):
"""Record every result for auditing. Does not modify the output."""
logger.info(
"[audit] request=%s %s.%s -> %r",
ctx.get("request_id"),
ctx.get("service"),
ctx.get("function"),
output,
)


def alert_on_sensitive(output, ctx):
"""Warn if a result appears to contain sensitive tokens.

Self-guards: only inspects string output; anything else is ignored.
Reacts (logs a warning) but does not change the output.
"""
if not isinstance(output, str):
return
for token in _SENSITIVE:
if token in output:
logger.warning(
"[alert] sensitive token %r in result of %s.%s (request=%s)",
token,
ctx.get("service"),
ctx.get("function"),
ctx.get("request_id"),
)
4 changes: 4 additions & 0 deletions tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ ORIG_CWD=$(pwd)
# Assuming the script was called from inside the ventis repo root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"

echo "-------------------------------------------"
echo ">> Running Output-Policy Unit Tests..."
python "$SCRIPT_DIR/test_output_policy.py" || exit 1

echo "-------------------------------------------"
echo ">> Running Integration Tests..."
python "$SCRIPT_DIR/test_integration.py" || exit 1
Expand Down
Loading