CrewAI integration
Five-minute path to wiring STACK into a CrewAI multi-agent crew. Each crew member gets its own STACK agent identity, its own passport, and its own scoped tools — so you can answer the question “which crew member did this?” from a single audit row, and revoking one agent doesn't take down the rest.
1. Install
pip install crewai getstackSTACK's Python SDK ships on PyPI as getstack.
2. Register each crew member as a STACK agent
import os
from getstack import Stack
stack = Stack(api_key=os.environ["STACK_API_KEY"])
researcher = stack.agents.register(
name="researcher",
description="Web research and source-gathering",
accountability_mode="enforced",
)
writer = stack.agents.register(
name="writer",
description="Drafts content from researcher findings",
accountability_mode="enforced",
)Each STACK agent is a separate identity in the audit log, with its own passport and its own scope. A leaked credential or a misbehaving crew member only burns the radius you grant that agent.
3. Connect the services each agent will need
Connect the upstream services at getstack.run/services. For this example: a search provider for the researcher and Notion for the writer. Each service is connected once at the operator level; both agents inherit access according to the passport scope you issue them.
4. Open a mission per agent
# Researcher mission — short-lived, scoped to search only
researcher_mission = stack.passports.mission(
agent_id=researcher.id,
intent="Gather sources on agent identity standards",
services=["serpapi"],
checkpoint_interval="5m",
)
# Writer mission — scoped to Notion only
writer_mission = stack.passports.mission(
agent_id=writer.id,
intent="Draft a Notion page from the researcher's findings",
services=["notion"],
checkpoint_interval="5m",
)stack.passports.mission(...) is a Python context manager — enter it with with and the passport is issued, checkpoints fire automatically, and checkout fires when the block exits. Scope only narrows down a mission's lifetime; nothing the crew does can widen it.
5. STACK-aware tools per agent
Each tool holds the calling agent's passport token and routes the actual upstream call through STACK's proxy.
from crewai.tools import BaseTool
from pydantic import ConfigDict
class StackProxyTool(BaseTool):
"""Generic STACK-proxied tool — supply name, description, service, url."""
model_config = ConfigDict(arbitrary_types_allowed=True)
name: str
description: str
service: str # provider slug, e.g. "notion"
target_url: str # full upstream URL
method: str = "POST"
stack_client: Stack
passport_token: str
def _run(self, **kwargs) -> dict:
response = self.stack_client.proxy.request(
service=self.service,
url=self.target_url,
method=self.method,
body=kwargs or None,
passport_token=self.passport_token,
)
if not response.ok():
raise RuntimeError(f"{self.service} call failed: {response.status}")
return response.body or {}stack.proxy.request(...) is the canonical proxy call. The proxy validates the URL (rejects relative paths), injects the credential server-side, runs scope + constraint checks against the passport, and returns a structured response with .status, .body, and .headers.
6. Wire the crew
from crewai import Agent, Task, Crew
with researcher_mission as r_run, writer_mission as w_run:
researcher_agent = Agent(
role="Researcher",
goal="Find 3 high-quality sources on agent identity standards",
backstory="Concise, factual, sources every claim",
tools=[
StackProxyTool(
name="search",
description="Search the web via SerpAPI",
service="serpapi",
target_url="https://serpapi.com/search.json",
method="GET",
stack_client=stack,
passport_token=r_run.token,
),
],
)
writer_agent = Agent(
role="Writer",
goal="Draft a 500-word brief in Notion citing the researcher's sources",
backstory="Clear prose, faithful to the brief",
tools=[
StackProxyTool(
name="create_notion_page",
description="Create a Notion page",
service="notion",
target_url="https://api.notion.com/v1/pages",
method="POST",
stack_client=stack,
passport_token=w_run.token,
),
],
)
research_task = Task(
description="Find 3 sources on agent identity standards.",
agent=researcher_agent,
expected_output="A list of 3 source URLs and a one-paragraph summary",
)
draft_task = Task(
description="Draft a Notion page summarizing the research.",
agent=writer_agent,
expected_output="A Notion page id",
)
crew = Crew(agents=[researcher_agent, writer_agent], tasks=[research_task, draft_task])
crew.kickoff()7. (Optional) Structured handoff via drop-offs
Free-form text passing between crew agents is fragile. Drop-offs let the producer declare a JSON Schema; the consumer collects once; the payload is destroyed after collection or expiry.
# Researcher creates a drop-off addressed to the writer
dropoff = stack.dropoffs.create(
from_agent_id=researcher.id,
to_agent_id=writer.id,
schema={
"type": "object",
"required": ["sources", "summary"],
"properties": {
"sources": {"type": "array", "items": {"type": "string"}},
"summary": {"type": "string", "maxLength": 4000},
},
},
ttl_seconds=600,
)
# Researcher deposits — agent_id is the depositing agent
stack.dropoffs.deposit(
dropoff_id=dropoff["id"],
data={"sources": ["https://...", "https://..."], "summary": "..."},
agent_id=researcher.id,
)
# Writer collects — agent_id is the collecting agent
findings = stack.dropoffs.collect(
dropoff_id=dropoff["id"],
agent_id=writer.id,
)Both deposit and collect require agent_id — STACK uses it to enforce the from/to addressing on the drop-off. Schema validation runs at deposit; the consumer is guaranteed schema-conforming data or no data at all.
8. Kill switch
# Revoke one agent's passport (sub-60s propagation)
stack.passports.revoke(researcher_mission.passport.jti, reason="off-script")
# Revoke every active passport for an agent
# (no SDK helper — call the API directly)
stack._client.post(f"/v1/passports/revoke-agent/{researcher.id}")See drop-offs concept and the passport API reference for the full contracts on cross-agent flows and revocation.
Why bother
- Each crew member is a separate STACK identity — audit rows tell you which agent did what
- Per-agent passports mean per-agent scope, per-agent revocation, per-agent detector signals
- Drop-offs replace free-form text passing with schema-validated, encrypted, one-read handoffs
- Sub-60-second kill switch per agent — the rest of the crew keeps running