Session Tools
Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history, and send to another session.Tool Names
sessions_listsessions_historysessions_sendsessions_spawn
Key Model
- Main direct chat bucket is always the literal key
"main"(resolved to the current agent’s main key). - Group chats use
agent:<agentId>:<channel>:group:<id>oragent:<agentId>:<channel>:channel:<id>(pass the full key). - Cron jobs use
cron:<job.id>. - Hooks use
hook:<uuid>unless explicitly set. - Node sessions use
node-<nodeId>unless explicitly set.
global and unknown are reserved values and are never listed. If session.scope = "global", we alias it to main for all tools so callers never see global.
sessions_list
List sessions as an array of rows. Parameters:kinds?: string[]filter: any of"main" | "group" | "cron" | "hook" | "node" | "other"limit?: numbermax rows (default: server default, clamp e.g. 200)activeMinutes?: numberonly sessions updated within N minutesmessageLimit?: number0 = no messages (default 0); >0 = include last N messages
messageLimit > 0fetcheschat.historyper session and includes the last N messages.- Tool results are filtered out in list output; use
sessions_historyfor tool messages. - When running in a sandboxed agent session, session tools default to spawned-only visibility (see below).
key: session key (string)kind:main | group | cron | hook | node | otherchannel:whatsapp | telegram | discord | signal | imessage | webchat | internal | unknowndisplayName(group display label if available)updatedAt(ms)sessionIdmodel,contextTokens,totalTokensthinkingLevel,verboseLevel,systemSent,abortedLastRunsendPolicy(session override if set)lastChannel,lastTodeliveryContext(normalized{ channel, to, accountId }when available)transcriptPath(best-effort path derived from store dir + sessionId)messages?(only whenmessageLimit > 0)
sessions_history
Fetch transcript for one session. Parameters:sessionKey(required; accepts session key orsessionIdfromsessions_list)limit?: numbermax messages (server clamps)includeTools?: boolean(default false)
includeTools=falsefiltersrole: "toolResult"messages.- Returns messages array in the raw transcript format.
- When given a
sessionId, Pllan resolves it to the corresponding session key (missing ids error).
Gateway session history and live transcript APIs
Control UI and gateway clients can use the lower level history and live transcript surfaces directly. HTTP:GET /sessions/{sessionKey}/history- Query params:
limit,cursor,includeTools=1,follow=1 - Unknown sessions return HTTP
404witherror.type = "not_found" follow=1upgrades the response to an SSE stream of transcript updates for that session
sessions.subscribesubscribes to all session lifecycle and transcript events visible to the clientsessions.messages.subscribe { key }subscribes only tosession.messageevents for one sessionsessions.messages.unsubscribe { key }removes that targeted transcript subscriptionsession.messagecarries appended transcript messages plus live usage metadata when availablesessions.changedemitsphase: "message"for transcript appends so session lists can refresh counters and previews
sessions_send
Send a message into another session. Parameters:sessionKey(required; accepts session key orsessionIdfromsessions_list)message(required)timeoutSeconds?: number(default >0; 0 = fire-and-forget)
timeoutSeconds = 0: enqueue and return{ runId, status: "accepted" }.timeoutSeconds > 0: wait up to N seconds for completion, then return{ runId, status: "ok", reply }.- If wait times out:
{ runId, status: "timeout", error }. Run continues; callsessions_historylater. - If the run fails:
{ runId, status: "error", error }. - Announce delivery runs after the primary run completes and is best-effort;
status: "ok"does not guarantee the announce was delivered. - Waits via gateway
agent.wait(server-side) so reconnects don’t drop the wait. - Agent-to-agent message context is injected for the primary run.
- Inter-session messages are persisted with
message.provenance.kind = "inter_session"so transcript readers can distinguish routed agent instructions from external user input. - After the primary run completes, Pllan runs a reply-back loop:
- Round 2+ alternates between requester and target agents.
- Reply exactly
REPLY_SKIPto stop the ping‑pong. - Max turns is
session.agentToAgent.maxPingPongTurns(0–5, default 5).
- Once the loop ends, Pllan runs the agent‑to‑agent announce step (target agent only):
- Reply exactly
ANNOUNCE_SKIPto stay silent. - Any other reply is sent to the target channel.
- Announce step includes the original request + round‑1 reply + latest ping‑pong reply.
- Reply exactly
Channel Field
- For groups,
channelis the channel recorded on the session entry. - For direct chats,
channelmaps fromlastChannel. - For cron/hook/node,
channelisinternal. - If missing,
channelisunknown.
Security / Send Policy
Policy-based blocking by channel/chat type (not per session id).sendPolicy: "allow" | "deny"(unset = inherit config)- Settable via
sessions.patchor owner-only/send on|off|inherit(standalone message).
chat.send/agent(gateway)- auto-reply delivery logic
sessions_spawn
Spawn a sub-agent run in an isolated session and announce the result back to the requester chat channel. Parameters:task(required)label?(optional; used for logs/UI)agentId?(optional; spawn under another agent id if allowed)model?(optional; overrides the sub-agent model; invalid values error)thinking?(optional; overrides thinking level for the sub-agent run)runTimeoutSeconds?(defaults toagents.defaults.subagents.runTimeoutSecondswhen set, otherwise0; when set, aborts the sub-agent run after N seconds)thread?(default false; request thread-bound routing for this spawn when supported by the channel/plugin)mode?(run|session; defaults torun, but defaults tosessionwhenthread=true;mode="session"requiresthread=true)cleanup?(delete|keep, defaultkeep)sandbox?(inherit|require, defaultinherit;requirerejects spawn unless the target child runtime is sandboxed)attachments?(optional array of inline files; subagent runtime only, ACP rejects). Each entry:{ name, content, encoding?: "utf8" | "base64", mimeType? }. Files are materialized into the child workspace at.pllan/attachments/<uuid>/. Returns a receipt with sha256 per file.attachAs?(optional;{ mountPath? }hint reserved for future mount implementations)
agents.list[].subagents.allowAgents: list of agent ids allowed viaagentId(["*"]to allow any). Default: only the requester agent.- Sandbox inheritance guard: if the requester session is sandboxed,
sessions_spawnrejects targets that would run unsandboxed.
- Use
agents_listto discover which agent ids are allowed forsessions_spawn.
- Starts a new
agent:<agentId>:subagent:<uuid>session withdeliver: false. - Sub-agents default to the full tool set minus session tools (configurable via
tools.subagents.tools). - Sub-agents are not allowed to call
sessions_spawn(no sub-agent → sub-agent spawning). - Always non-blocking: returns
{ status: "accepted", runId, childSessionKey }immediately. - With
thread=true, channel plugins can bind delivery/routing to a thread target (Discord support is controlled bysession.threadBindings.*andchannels.discord.threadBindings.*). - After completion, Pllan runs a sub-agent announce step and posts the result to the requester chat channel.
- If the assistant final reply is empty, the latest
toolResultfrom sub-agent history is included asResult.
- If the assistant final reply is empty, the latest
- Reply exactly
ANNOUNCE_SKIPduring the announce step to stay silent. - Announce replies are normalized to
Status/Result/Notes;Statuscomes from runtime outcome (not model text). - Sub-agent sessions are auto-archived after
agents.defaults.subagents.archiveAfterMinutes(default: 60). - Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
Sandbox Session Visibility
Session tools can be scoped to reduce cross-session access. Default behavior:tools.sessions.visibilitydefaults totree(current session + spawned subagent sessions).- For sandboxed sessions,
agents.defaults.sandbox.sessionToolsVisibilitycan hard-clamp visibility.
self: only the current session key.tree: current session + sessions spawned by the current session.agent: any session belonging to the current agent id.all: any session (cross-agent access still requirestools.agentToAgent).- When a session is sandboxed and
sessionToolsVisibility="spawned", Pllan clamps visibility totreeeven if you settools.sessions.visibility="all".