Give a CrewAI agent a tool that runs through STACK. Its Mission requests the connections and actions needed for the task. STACK checks that request against the fleet policy and issues the Passport used by the tool.
pip install crewai getstackFollow Deploying agents to connect Slack, approve the fleet policy and enroll the worker. Set STACK_AGENT_ID to that worker’s ID and keep its enrolled key available to the runtime. The operator’s credential stays in the setup process.
Save the shared tool as fleet_support_tool.py and the Mission request as mission.json. Replace the policy and connection IDs with your own, and use the channel allowed by your policy. Keep the job’s idempotency key when retrying that job.
Configure the framework’s model provider separately. This example routes the Slack tool through STACK. Its model calls use the framework’s own connection.
import hashlib
def start_support_job(stack, request, provider_url="https://slack.com/api/chat.postMessage"):
started = stack.missions.start(request)
operation_id = hashlib.sha256(
(started["mission_id"] + ":support-update").encode()
).hexdigest()
sent = False
def post_support_update(text: str) -> dict:
"""Post this job's support update to Slack channel C123."""
nonlocal sent
response = stack.proxy.request(
"slack", provider_url, "POST",
body={"channel": "C123", "text": text},
passport_token=started["passport"]["token"],
authority_request_id=operation_id,
)
if response.status != 200 or not response.body.get("ok", False):
raise RuntimeError("Slack did not accept the update")
sent = True
return response.body
def finish():
if not sent:
raise RuntimeError("The support update has not been sent")
stack.passports.checkout(
started["passport"]["jti"], services_used=["slack"],
actions_count=1, summary="Posted the support update",
)
return stack.missions.complete(started["mission_id"], output={"done": True})
def stop():
return stack.missions.revoke(started["mission_id"], reason="operator_initiated_clean")
return started, post_support_update, finish, stop
{
"fleet_policy_id": "flp_your_policy",
"idempotency_key": "support-ticket-4821-update",
"intent_summary": "Post the support update for ticket 4821",
"requested_access": [
{
"connection_id": "scon_your_slack_account",
"scopes": [
"chat:write"
],
"intents": [
{
"name": "slack.post_to_channel",
"params": {
"channel": "C123"
}
}
],
"constraints": [],
"allow_data_from": []
}
],
"requested_operations": [],
"limits": {
"max_actions": 20,
"max_concurrent_actions": 1,
"max_duration_seconds": 900
},
"expected_output_shape": {
"type": "object",
"required": [
"done"
],
"properties": {
"done": {
"const": true
}
},
"additionalProperties": false
}
}import json
import os
from getstack import Stack
from fleet_support_tool import start_support_job
stack = Stack(agent_id=os.environ["STACK_AGENT_ID"], base_url=os.environ.get("STACK_API_URL", "https://api.getstack.run"))
with open("mission.json", encoding="utf-8") as source:
request = json.load(source)
started, post_support_update, finish, stop = start_support_job(stack, request)
from crewai import Agent, Task, Crew
from crewai.tools import tool
updater = Agent(
role="Support updater",
goal="Post the supplied support update once",
backstory="You report confirmed ticket outcomes.",
tools=[tool("post_support_update")(post_support_update)],
allow_delegation=False,
)
task = Task(
description="Post this update once: Ticket 4821 has been resolved.",
expected_output="Confirmation that the update was posted",
agent=updater,
)
try:
Crew(agents=[updater], tasks=[task]).kickoff()
finish()
except Exception:
stop()
raise
Starting the Mission returns its ID and Passport. The shared tool sends that Passport on the Slack request. STACK checks the endpoint, channel and live limits before adding the stored credential. A request for another channel is refused even if the tool is changed.
The example posts one update per job. Its operation ID stays the same across retries, so an unchanged retry cannot send another message. Give a genuinely different action its own operation ID.
This example uses an allow rule and stops the Mission on any error. A review rule instead returns an approval reference before dispatch. A runtime handling review must keep the Mission open while it waits, then retry the unchanged request after approval. That approval permits one action and does not expand the fleet policy.
For a larger Crew, enroll each worker under the approved fleet policy and start its Mission with the access its task needs. The role name and expected_output text do not grant permissions. Separate Missions can share the fleet’s allowance while keeping their own usage and records.
The example checks out the Passport and completes the Mission after the tool confirms delivery. An error revokes the Mission so it cannot accept further calls.
For work that continues beyond this short example, record checkpoints and use stack.missions.renew(mission_id) to obtain the next Passport. Use the returned token for later calls. Renewal retains the same Mission, scope and usage.
Revoke a Mission to stop that job, or revoke fleet membership to stop one worker’s fleet access. STACK rejects the next call or renewal that depends on revoked access.