STACK
MENU
DOCS / INTEGRATIONS / LANGCHAIN

LangChain integration

Expose a STACK proxy call as a LangChain tool. Each job starts a Mission with the access it needs, and tool calls use its Passport. The connected service credential stays in STACK.

Install

bash
pip install langchain langchain-openai getstack

Prepare the fleet

Follow 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.

fleet_support_tool.py

python
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

mission.json

json
{
  "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
  }
}

Start the job and use the tool

python
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 langchain_core.tools import tool
from langchain.agents import create_agent

agent = create_agent(
    model="openai:gpt-4o-mini",
    tools=[tool(post_support_update)],
    system_prompt="Post the supplied support update once using the tool.",
)
try:
    agent.invoke({"messages": [{"role": "user", "content": "Ticket 4821 has been resolved."}]})
    finish()
except Exception:
    stop()
    raise

How access is enforced

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.

Finish, renew or stop

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.

stack | Docs