Add Tenuo authorization contrib module#1447
Add Tenuo authorization contrib module#1447aimable100 wants to merge 2 commits intotemporalio:mainfrom
Conversation
Adds `temporalio.contrib.tenuo`, a SimplePlugin integration for
Tenuo warrant-based authorization in Temporal workflows.
The plugin (`TenuoPlugin`) wires client interceptors, worker
interceptors, and workflow sandbox passthrough in a single line:
from temporalio.contrib.tenuo import TenuoPlugin
plugin = TenuoPlugin(config)
client = await Client.connect("localhost:7233", plugins=[plugin])
Key design decisions:
- Thin adapter: only TenuoPlugin, TENUO_PLUGIN_NAME, and
ensure_tenuo_workflow_runner are exported from the contrib module.
All other types (TenuoPluginConfig, EnvKeyResolver, etc.) are
imported directly from tenuo.temporal.
- No private imports: all tenuo.temporal internals used by the plugin
are exposed through public lazy-loaded names.
- No re-exports of external package types, matching the pattern
established by openai_agents and other contrib modules.
Files:
- temporalio/contrib/tenuo/__init__.py — public API (3 exports)
- temporalio/contrib/tenuo/_plugin.py — TenuoPlugin SimplePlugin subclass
- temporalio/contrib/tenuo/README.md — multi-agent delegation example
- tests/contrib/tenuo/test_tenuo.py — unit + live integration tests
- tests/contrib/tenuo/test_tenuo_replay.py — record-and-replay tests
- pyproject.toml — tenuo optional dependency
Add Tenuo authorization contrib module
|
I don't think it is likely that we are willing to accept this. We welcome folks using Temporal as a part of their solution, but including it in the SDK's contrib comes with an implication of our maintenance and ownership of the solution. From a technical perspective, you are welcome to create a plugin external to the SDK repo, and we can have a discussion about partnership. If you reach out in our community slack, I can put you in touch with the folks running AI partnership. |
|
Thanks for the note. This was submitted through Temporal's AI Partner Program — I was invited and completed the submission form. Happy to move to an external plugin if that's the preferred path for partners too. Will follow up with the team to confirm. |
|
@aimable100 - thank you for preparing this plugin. I will leave this PR open so that our team can provide feedback. You should plan to move it to one of your repositories, though. |
|
Warrant-based authorization with capability attenuation for child workflows is exactly the right model for multi-agent systems — the core invariant (sub-agent warrants can only be narrower than parent warrants) prevents privilege escalation across delegation chains, which is one of the hardest problems to enforce in hierarchical agent systems. A few observations on the design: Warrant as behavioral trust anchor. The warrant proves what an agent is authorized to do. Combining it with a behavioral trust score (SATP or AgentFolio) proves what the agent has reliably done in practice. Together they answer both "is this agent authorized?" and "has this agent been trustworthy?". The warrant = tenuo.create_warrant(
tools=["run_query", "send_report"],
agent_id="fleet-agent-xyz",
behavioral_trust_url="https://agentfolio.bot/verify/fleet-agent-xyz" # optional
)Denial receipt propagation. When Replay safety note. The record-and-replay determinism approach via |
Summary
Adds
temporalio.contrib.tenuo, aSimplePluginthat wires Tenuo warrant-based authorization into Temporal workflows. Agents (workflows) carry signed warrants specifying which tools (activities) they can call and with what argument constraints. Sub-agents (child workflows) receive attenuated warrants — capabilities can only shrink, never expand.TenuoPlugin— registers client interceptor (warrant header injection), worker interceptors (PoP signing + authorization verification), and sandbox passthrough for thetenuonative extension.TenuoPlugin,TENUO_PLUGIN_NAME,ensure_tenuo_workflow_runner). All other types are imported fromtenuo.temporal, matching the pattern established byopenai_agentsand other contrib modules.tenuo.temporalinternals used by the plugin are exposed through public lazy-loaded names.Files
temporalio/contrib/tenuo/__init__.pytemporalio/contrib/tenuo/_plugin.pytemporalio/contrib/tenuo/README.mdtests/contrib/tenuo/test_tenuo.pytests/contrib/tenuo/test_tenuo_replay.pypyproject.tomlReplay safety
Replay determinism is verified at two levels:
workflow.now()(nottime.time()), nodatetime.now(), noos.urandom/random/uuid4, notime.sleep, nothreading.Thread.fetch_history(), and a freshTenuoPlugininstance replays viaReplayer. Tests cover single-tool and multi-tool (sequential PoP ordering) scenarios.Integration tests
test_authorized_activity_succeeds— full warrant → PoP → authorization flowtest_start_workflow_authorized—start_workflow_authorizedreturns a handletest_unauthorized_activity_is_non_retryable— unauthorized tool call producesWorkflowFailureErrorwithApplicationError(non_retryable=True)test_duplicate_registration_raises— same plugin instance on two workers raisesRuntimeErrorTest plan
pytest tests/contrib/tenuo/test_tenuo.py -v— unit + integration testspytest tests/contrib/tenuo/test_tenuo_replay.py -v— replay determinism testsruff check temporalio/contrib/tenuo/ tests/contrib/tenuo/— no lint errors