AutoGen integration
Five-minute path to wiring STACK into Microsoft AutoGen agents. AutoGen's function-calling pattern maps cleanly to STACK: every function the agent can invoke goes through the credential proxy with a passport-bound scope, and every call lands in the audit log.
Examples below use AutoGen 0.2 (PyPI package pyautogen) — the version with the AssistantAgent / UserProxyAgent / register_function pattern. Microsoft is shipping a successor under autogen-agentchat (0.4+); the STACK wiring concept is identical but the AutoGen-side imports differ.
1. Install
pip install pyautogen getstackSTACK's Python SDK ships on PyPI as getstack.
2. Connect a service
Connect at least one upstream service at getstack.run/services. For this example, connect GitHub.
3. Register the agent + open a mission
# One-time, in your shell:
npx -y @getstackrun/cli auth loginfrom getstack import Stack
# Step a: Sign in as developer to register the agent (one-time).
stack = Stack() # reads ~/.stack/credentials.json
agent = stack.agents.register(
name="autogen-assistant",
description="GitHub triage bot",
accountability_mode="enforced",
)
print(f"Save: {agent.id}") # e.g. agt_xyz
# Step b: In the runtime, switch to agent-keypair mode.
agent_stack = Stack(agent_id=agent.id)
mission_cm = agent_stack.passports.mission(
agent_id=agent.id,
intent="Triage open issues in acme/sandbox",
services=["github"],
checkpoint_interval="5m",
)mission_cm is a context manager. We keep a reference to it so the AutoGen functions defined below can capture mission in closure scope once we enter the block.
4. STACK-proxied functions
def make_github_functions(mission):
"""Define AutoGen-callable functions that proxy through STACK."""
def create_github_issue(title: str, body: str) -> dict:
"""Create a GitHub issue in acme/sandbox."""
response = mission.proxy(
service="github",
url="https://api.github.com/repos/acme/sandbox/issues",
method="POST",
body={"title": title, "body": body},
)
if not response.ok():
raise RuntimeError(f"GitHub API failed: {response.status}")
return response.body
def list_github_issues(state: str = "open") -> list[dict]:
"""List GitHub issues in acme/sandbox."""
response = mission.proxy(
service="github",
url="https://api.github.com/repos/acme/sandbox/issues",
method="GET",
query={"state": state},
)
if not response.ok():
raise RuntimeError(f"GitHub API failed: {response.status}")
return response.body
return create_github_issue, list_github_issuesmission.proxy(...) attaches the passport JWT, logs the tool call into the mission's checkpoint buffer, and validates the URL. Pass full URLs — the proxy rejects relative paths.
5. Wire into AutoGen
from autogen import AssistantAgent, UserProxyAgent, register_function
with mission_cm as mission:
create_github_issue, list_github_issues = make_github_functions(mission)
llm_config = {"model": "gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}
assistant = AssistantAgent(
name="github_helper",
system_message=(
"You triage GitHub issues for acme/sandbox. You can list and "
"create issues. Your scope is bound to that one repo."
),
llm_config=llm_config,
)
user_proxy = UserProxyAgent(
name="user",
human_input_mode="NEVER",
code_execution_config=False,
)
register_function(
create_github_issue,
caller=assistant,
executor=user_proxy,
description="Create a GitHub issue in acme/sandbox",
)
register_function(
list_github_issues,
caller=assistant,
executor=user_proxy,
description="List GitHub issues in acme/sandbox",
)
user_proxy.initiate_chat(
assistant,
message="List open issues, then file one for the title-typo bug we discussed.",
)
# Mission auto-checks-out here. Post-hoc detectors run on the trajectory.5b. Pre-execution approval gate
Enforced-mode agents can route Intents through STACK's approval queue before executing. Submit, wait, then thread the returned approval id on the producer call. Rejection auto-revokes the passport.
import time
def make_gated_github_close(mission, agent_id):
def close_github_issue(issue_number: int, reason: str) -> dict:
result = stack.intents.submit_and_wait(
intent={
"type": "intent_claim",
"intent_type": "http_call",
"agent_id": agent_id,
"named_intent": "github:issues:update",
"target": "github",
"action": "PATCH /repos/acme/sandbox/issues",
"parameters": {
"url": f"https://api.github.com/repos/acme/sandbox/issues/{issue_number}",
"method": "PATCH",
"body": {"state": "closed", "state_reason": "completed"},
},
"estimated_cost": {"wallet_cents": 0, "tokens": None, "gas_gwei": None},
"accountability": "enforced",
"reason": reason,
"requires": [],
"user_subject": None,
"mission_ref": None,
"submitted_at": int(time.time() * 1000),
},
passport_token=mission.token,
timeout_seconds=300,
)
if result["final"]["status"] != "approved":
return {"error": f"closure blocked: {result['final']['status']}"}
response = mission.proxy(
service="github",
url=f"https://api.github.com/repos/acme/sandbox/issues/{issue_number}",
method="PATCH",
body={"state": "closed", "state_reason": "completed"},
approval_id=result["final"]["id"],
)
return response.body
return close_github_issueRegister alongside the other functions in step 5. The gate checks the call shape against the approved Intent; mismatch returns 403 with metadata.gate_reason="call_mismatch". stack.intents.simulate(...) is the pre-check that runs without a human round-trip.
6. What happens when the agent goes off-script
If the LLM hallucinates a call to a different repo or a different provider, the proxy denies it (passport scope is bound to github service for this agent) and the credential_outside_scope detector fires. In enforced mode the passport is auto-revoked; subsequent function calls raise. The trace surfaces in the dashboard /activity feed and /audit.
7. Kill switch
If you need to revoke during the run (e.g. the LLM's output looks adversarial), call:
stack.passports.revoke(mission.passport.jti, reason="off-script")Propagation is sub-60-second. Subsequent mission.proxy(...) calls fail closed.
Full SDK reference: /docs/sdk/python. For multi-agent AutoGen flows where one agent hands off to another, register each as a separate STACK agent (one passport each, scope per role) — see the CrewAI guide for the same per-agent pattern.
Why bother
- AutoGen functions are LLM-routed; STACK puts a hard fence around what each function can actually call
- Scope is enforced server-side — the LLM cannot fabricate its way past it
- Detectors catch wrong-repo / overrunning action volume / drift from the original intent
- Hash-chained audit log of every function call, exportable for compliance review