Skip to content

Capsule Session Lifecycle

Every role container runs the jackin’ Capsule (jackin-capsule) as PID 1. The daemon owns the PTY sessions, renders the in-container multiplexer, serves /jackin/run/jackin.sock, and exits when the last live session ends. The host attaches with a separate docker exec -it <container> /jackin/runtime/jackin-capsule client. See jackin’ Capsule for the control-plane design and wire protocol.

There are two session types:

Session typeAgent slugCommand source
Agent paneclaude, codex, amp, kimi, or opencode/jackin/runtime/entrypoint.sh
Shell panenone/bin/zsh

JACKIN_AGENT is per agent pane. The daemon sets it only when spawning /jackin/runtime/entrypoint.sh; shell panes do not receive it.

When jackin load provisions a new instance, the container launch happens in two steps:

  1. The launcher writes ~/.jackin/sockets/<container>/agent.toml; Docker mounts that directory at /jackin/run, so PID 1 reads the same file as /jackin/run/agent.toml.
  2. docker run -d ... <image> <agent> starts the container detached. The image entrypoint is /jackin/runtime/jackin-capsule; the trailing <agent> argv tells PID 1 which initial agent tab to spawn. It is not exported as a container environment variable.
  3. The launcher checks that PID 1 did not exit before attach. If it exited, the launcher captures the last 40 lines of container logs and surfaces an actionable error.
  4. The launcher runs docker exec -it <container> /jackin/runtime/jackin-capsule. The client connects to the daemon socket and blocks the operator terminal for the duration of the attach.

Implementation: src/runtime/launch.rs (launch_role_runtime, diagnose_premature_exit) and crates/jackin-capsule/src/main.rs (resolve_initial_agent).

jackin hardline inspects the target container and chooses the reconnect path:

  • Container is running: attach with docker exec -it <container> /jackin/runtime/jackin-capsule.
  • Container is running and the operator requested a new agent session: run docker exec --workdir <workdir> -it <container> /jackin/runtime/jackin-capsule new <agent>. The agent slug travels as argv; JACKIN_AGENT is set later inside the spawned PTY.
  • Container is running and the operator requested a shell: run docker exec -it <container> /jackin/runtime/jackin-capsule new. No agent slug means a shell pane.
  • Container is stopped with crash state: restore the required DinD sidecar, network, and certs when needed, restart the role container, then attach.
  • Container is gone but indexed recoverable state exists: rebuild the runtime around jackin-managed local state.

Implementation: src/runtime/attach.rs (reconnect_or_create_session, spawn_agent_session, spawn_shell_session).

jackin’ queries the daemon through its socket by executing:

Terminal window
/jackin/runtime/jackin-capsule status

The command opens the Capsule control channel, sends a typed Status request, and prints a line-oriented summary that older host paths parse into AgentSession records. Host console previews use the same control channel with a JSON Snapshot request: same-kernel Docker hosts connect directly to the bind-mounted socket, while Docker Desktop hosts fall back to docker exec ... /jackin/runtime/jackin-capsule snapshot because the host cannot connect to a Linux VM Unix socket directly. If the container is not running, inventory is reported as sessions:not_running without contacting Docker.

Implementation: src/runtime/attach.rs (inspect_agent_sessions) and crates/jackin-capsule/src/client.rs (run_status).

  1. An agent or shell process exits.
  2. The daemon removes the pane and redraws the multiplexer.
  3. If no live sessions remain, PID 1 drains the final frame and exits.
  4. The host finalizer observes the stopped container and runs cleanup:
    • docker rm -f <role-container>
    • docker rm -f <dind-container>
    • docker volume rm <certs-volume>
    • docker network rm <network>
  5. Keep-awake state is reconciled after the managed containers are gone.

The palette Exit command deliberately drives this same shutdown shape. It asks for confirmation, terminates every live pane, lets PID 1 exit once the session set is empty, and then the reconnect/attach finalizer removes the Docker resources with the normal eject cleanup path.

When the operator detaches or the terminal closes while sessions are still alive, the attach client exits but PID 1 and the session PTYs continue running. jackin hardline can reconnect to the daemon later. The DinD sidecar, network, and certs volume remain while the role container is preserved. This is separate from Exit: detach is the keep-running path, while Exit is the stop-and-clean-up path.

If the role container exits non-zero or is OOM-killed, jackin’ records crash state and removes the DinD sidecar, network, and certs volume. If Docker sends SIGTERM to the role container, PID 1 shuts down the daemon and sessions as the container namespace exits.

ThingPersists?Notes
Agent conversation historyYesStored in the per-instance durable home mount on the host
Agent auth tokensYes (per auth mode)Depends on the configured auth forwarding mode
Files written in the mounted workspaceYesHost-side mount
In-container packages installed ad hocNoWritable layer is lost on container stop/removal
DinD imagesNoDinD state is recreated with the sidecar
Capsule session layout/stateNoSessions are recreated by hardline --new or the next load
jackin load
-> docker run -d ... <image> claude
-> jackin-capsule PID 1 spawns initial Claude PTY
-> docker exec -it <container> jackin-capsule
-> attach client renders the daemon session
running container
-> hardline
-> docker exec -it <container> jackin-capsule
-> hardline --new --agent codex
-> docker exec -it <container> jackin-capsule new codex
-> daemon spawns Codex PTY with JACKIN_AGENT=codex
-> hardline --shell
-> docker exec -it <container> jackin-capsule new
-> daemon spawns zsh PTY without JACKIN_AGENT
last live PTY exits
-> daemon exits
-> Docker reports container stopped
-> host cleanup removes role container, DinD, cert volume, and network