- [Interactive Sessions](#interactive-sessions)
Advanced Session Management
Table of Contents
- Interactive Sessions
- Session Lifecycle API
- Buffer Management
- Session Configuration Tips
- Common Workflows
- Next Steps
Interactive Sessions
StdioExecutor supports long-lived processes so the model can maintain environment state across
multiple PCP calls—for example, starting bash, running cd, then compiling inside the same shell.
Interactive mode keeps the process alive, tracks per-session buffers, and exposes handles you can
tunnel back into later. The keepSessionAlive flag is now honoured directly by the executor, so a
session stays active after the first command only when you opt in. Likewise, bufferPersistence
controls whether IO is captured for reconnect or replay flows—set it intentionally because it has a
direct impact on memory usage.
Create an interactive option by switching the execution mode:
import com.TTT.PipeContextProtocol.StdioContextOptions
import com.TTT.PipeContextProtocol.StdioExecutionMode
import com.TTT.PipeContextProtocol.Permissions
val shell = StdioContextOptions().apply {
command = "bash"
executionMode = StdioExecutionMode.INTERACTIVE
keepSessionAlive = true
bufferPersistence = true
permissions.addAll(listOf(Permissions.Read, Permissions.Write, Permissions.Execute))
timeoutMs = 120_000
}
StdioExecutor returns a PcpRequestResult whose output string contains the generated
sessionId and optional bufferId. Subsequent PCP requests can reconnect by setting
executionMode = CONNECT and providing the same sessionId. When reconnection or buffer replay is
enabled you should also flip PcpContext.enableSessionAccessControl or
enableBufferAccessControl—the executor now validates ownership and the permissions declared in the
matching StdioContextOptions. If the context does not contain an entry for the command, buffer
writes are rejected instead of falling back to a permissive default.
Session Lifecycle API
Under the hood, interactive requests are backed by StdioSessionManager. You can use the manager
directly when you need programmatic control outside PCP.
import com.TTT.PipeContextProtocol.StdioSessionManager
import com.TTT.PipeContextProtocol.SessionResponse
val manager = StdioSessionManager
suspend fun startSession(): String {
val session = manager.createSession(command = "bash", args = emptyList(), ownerId = "user123", workingDir = null)
return session.sessionId
}
suspend fun runCommand(sessionId: String, command: String): SessionResponse {
return manager.sendInput(sessionId, command)
}
suspend fun readPending(sessionId: String): String {
return manager.readOutput(sessionId, timeoutMs = 2_000)
}
fun close(sessionId: String) {
manager.closeSession(sessionId)
}
Key types:
StdioSession: metadata about the running process (command, args,bufferId, timestamps).SessionResponse: output, error text (if any), andisActiveindicator returned bysendInput.SessionResult: convenience wrapper used by the executor when creating sessions.
listActiveSessions() surfaces the currently managed session IDs so you can monitor or shut them
down proactively.
Buffer Management
When bufferPersistence = true, StdioExecutor routes IO through StdioBufferManager. Buffers are
plain in-memory archives keyed by bufferId. Access checks respect the context: a caller needs
Permissions.Read to read a buffer and Permissions.Write to append new entries whenever
enableBufferAccessControl is active. If those permissions are missing the append call fails with a
security exception, so make sure the LLM’s context entry mirrors how you expect the session to be
used.
import com.TTT.PipeContextProtocol.StdioBufferManager
import com.TTT.PipeContextProtocol.BufferDirection
val buffers = StdioBufferManager()
val buffer = buffers.createBuffer(sessionId)
buffers.appendToBuffer(buffer.bufferId, "ls -la", BufferDirection.INPUT)
buffers.appendToBuffer(buffer.bufferId, "total 4\nREADME.md", BufferDirection.OUTPUT)
val snapshot = buffers.getBuffer(buffer.bufferId)
println("Entries captured: ${snapshot?.entries?.size}")
searchBuffer performs a case-insensitive substring match and returns BufferMatch objects with the
entry index and the original BufferEntry. Use entry.timestamp or entry.metadata for deeper
analysis.
val matches = buffers.searchBuffer(buffer.bufferId, "error")
matches.forEach { match ->
println("Found at entry ${match.entryIndex}: ${match.entry.content}")
}
To persist or restore session history, use saveBuffer and loadBuffer. Both rely on
serialize/deserialize helpers inside the TPipe util package.
Session Configuration Tips
- Timeouts –
StdioContextOptions.timeoutMsapplies to each interaction. Choose a generous value for long-running builds and a tighter one for simple inspection commands. - Buffer size – Cap
maxBufferSizeto keep runaway output from exhausting memory. The executor truncates output once the threshold is reached. - Access control flags – Enable
enableSessionAccessControlandenableBufferAccessControlin thePcpContextwhenever you rely onkeepSessionAliveor persisted buffers. Both toggles now enforce ownership and permission checks instead of acting as placeholders. - Environment variables –
environmentVariablesoverrides inherited variables per session, which is useful when bootstrapping toolchains. - Working directories – set
workingDirectoryto control where the process starts. Combine this withPcpContext.allowedDirectoryPathsto stop the model from escaping its sandbox. - Cleanup – call
closeSessionfor idle sessions. The manager destroys the process and releases buffered resources.
Common Workflows
- Stateful REPLs – expose Python, Node, or SQL shells with interactive mode, then pipe follow-up
commands through
CONNECTrequests. - Long builds – run
makeorgradleonce, keep the session alive, and feed incremental tasks or log scraping commands without reinitialising the environment. - Audit trails – persist buffers to disk for compliance or debugging. Pair the JSON output with your logging pipeline to review every command the model executed.
Next Steps
→ Conversation History Management - Managing conversation storage utilities used alongside PCP