What's here today
AgentGovernancePlugin (this repo, plugins/agent_governance_plugin.py) already
proves the shape works: a BasePlugin that evaluates policy-as-code before a
tool call and short-circuits denied calls. It wraps Microsoft's
Agent Governance Toolkit
(agentmesh-platform).
What I'd like to add
A second plugin in the same slot, TulipGovernancePlugin, backed by
tulipagents.ai (tulip-agents on PyPI, Apache-2.0, open source).
Same idea — evaluate a policy before a tool call, short-circuit when it doesn't
clear — with two differences worth having as a second option rather than a
replacement:
- Three outcomes, not two. Tuliplabs'
approve() returns allow / require a
human / deny. This plugin does not collapse require_human into either of
the other two — it returns a distinct held_for_approval response, separate
from policy_denied, so a caller can route "needs a person" differently from
"refused."
- A tamper-evident audit trail. Every decision — allow included — is
appended to a SHA-256 hash-chained AuditTrail (tulip.security.audit), so
a denied or held call is recorded exactly as durably as one that proceeded,
and the chain breaks verifiably if a record is edited after the fact.
Testing plan (already done, happy to move the PR)
- 9 unit tests against the real
tulip-agents package (not a mock — the
admission-gate primitives it uses are pure dataclass logic with no LLM or
network dependency, so there's nothing to fake).
- 6 end-to-end tests through a real
LlmAgent + InMemoryRunner, not by
calling the plugin's method directly — proving, through ADK's actual
PluginManager/functions.py dispatch, that a denied or held call's tool
body never runs (checked via an observable side effect, not just the return
value), that an allowed call's tool body genuinely executes, that two calls
in one turn are gated independently, and that a raising action-builder fails
closed rather than degrading to allow.
- 100% line and branch coverage on the new module.
pyink/mypy --config-file pyproject.toml clean.
Scope
require_human short-circuits the call — it does not pause the run and wait
for a person to decide, then let the original call proceed. Wiring that
through this repo's own tools/hitl gateway (or Tuliplabs' own approval bridge)
is a natural follow-up, not done here.
PR to follow, referencing this issue.
What's here today
AgentGovernancePlugin(this repo,plugins/agent_governance_plugin.py) alreadyproves the shape works: a
BasePluginthat evaluates policy-as-code before atool call and short-circuits denied calls. It wraps Microsoft's
Agent Governance Toolkit
(
agentmesh-platform).What I'd like to add
A second plugin in the same slot,
TulipGovernancePlugin, backed bytulipagents.ai (
tulip-agentson PyPI, Apache-2.0, open source).Same idea — evaluate a policy before a tool call, short-circuit when it doesn't
clear — with two differences worth having as a second option rather than a
replacement:
approve()returns allow / require ahuman / deny. This plugin does not collapse
require_humaninto either ofthe other two — it returns a distinct
held_for_approvalresponse, separatefrom
policy_denied, so a caller can route "needs a person" differently from"refused."
appended to a SHA-256 hash-chained
AuditTrail(tulip.security.audit), soa denied or held call is recorded exactly as durably as one that proceeded,
and the chain breaks verifiably if a record is edited after the fact.
Testing plan (already done, happy to move the PR)
tulip-agentspackage (not a mock — theadmission-gate primitives it uses are pure dataclass logic with no LLM or
network dependency, so there's nothing to fake).
LlmAgent+InMemoryRunner, not bycalling the plugin's method directly — proving, through ADK's actual
PluginManager/functions.pydispatch, that a denied or held call's toolbody never runs (checked via an observable side effect, not just the return
value), that an allowed call's tool body genuinely executes, that two calls
in one turn are gated independently, and that a raising action-builder fails
closed rather than degrading to allow.
pyink/mypy --config-file pyproject.tomlclean.Scope
require_humanshort-circuits the call — it does not pause the run and waitfor a person to decide, then let the original call proceed. Wiring that
through this repo's own
tools/hitlgateway (or Tuliplabs' own approval bridge)is a natural follow-up, not done here.
PR to follow, referencing this issue.