Salambo delivers message text as the normal agent input. For integration-triggered turns, tools and hooks also receive bounded channel metadata in ctx.external.

Branch by provider

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

    if (!external) {
      return;
    }

    const instruction =
      external.provider === "slack"
        ? "Use short paragraphs suitable for Slack."
        : external.provider === "microsoft_teams"
          ? "Use a concise Teams-friendly response."
          : "Use a concise chat response.";

    return {
      systemPrompt: `${event.systemPrompt}\n\n${instruction}`,
    };
  });
}
For API, playground, scheduled, or other non-channel turns, ctx.external is null.

Use conversation identity in a tool

pi.registerTool({
  name: "current_conversation",
  description: "Return the current external conversation identity.",
  parameters: {
    type: "object",
    properties: {},
    additionalProperties: false,
  },
  async execute(_id, _params, _signal, _update, ctx) {
    if (!ctx.external) {
      return {
        content: [
          { type: "text", text: "This turn is not from a chat integration." },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            provider: ctx.external.provider,
            conversationId: ctx.external.conversationId,
            conversationType: ctx.external.conversationType,
          }),
        },
      ],
    };
  },
});

Security boundary

ctx.external contains identifiers and routing metadata. It does not contain:
  • Slack or Teams access tokens;
  • signing secrets or client secrets;
  • provider credentials;
  • the message text;
  • arbitrary raw webhook bodies.
Treat identifiers as customer metadata. Do not publish them in artifacts or external logs unless the product requires it. See the exact external event context, then choose a channel-aware recipe.