These recipes use ctx.external while keeping message transport, authentication, deduplication, and replies owned by Salambo.

Slack thread-aware instructions

export default function slackBehavior(pi) {
  pi.on("before_agent_start", (event, ctx) => {
    const external = ctx.external;

    if (external?.provider !== "slack") {
      return;
    }

    const location = external.refs.threadTs
      ? "an existing Slack thread"
      : "a new Slack conversation";

    return {
      systemPrompt: `${event.systemPrompt}\n\nThis request came from ${location}. Reply concisely and do not repeat the full question.`,
    };
  });
}

Teams channel-aware instructions

export default function teamsBehavior(pi) {
  pi.on("before_agent_start", (event, ctx) => {
    const external = ctx.external;

    if (external?.provider !== "microsoft_teams") {
      return;
    }

    const audience = external.refs.channelId
      ? "a Teams channel"
      : "a direct Teams conversation";

    return {
      systemPrompt: `${event.systemPrompt}\n\nThe audience is ${audience}. Keep the answer clear for everyone in that conversation.`,
    };
  });
}

Restrict a sensitive tool to direct messages

pi.on("tool_call", (event, ctx) => {
  if (event.toolName !== "lookup_private_customer_record") {
    return;
  }

  if (ctx.external && ctx.external.conversationType !== "dm") {
    return {
      block: true,
      reason:
        "Private customer records can only be queried from a direct message.",
    };
  }
});
Decide separately whether API-originated turns (ctx.external === null) are allowed. The example allows them.

Attach routing metadata to tool details

async function execute(_id, params, _signal, _update, ctx) {
  const external = ctx.external;

  return {
    content: [{ type: "text", text: "Request recorded." }],
    details: {
      requestId: params.requestId,
      provider: external?.provider ?? null,
      conversationId: external?.conversationId ?? null,
    },
  };
}
Do not copy provider tokens, complete messages, or unnecessary sender identifiers into tool results.

Preserve transport ownership

Extensions should adapt agent behavior, not send Slack or Teams replies directly. Salambo owns webhook verification, idempotency, conversation mapping, response creation, and provider delivery. For setup, use the Slack guide or Microsoft Teams guide.