context: thread daemon ctx through sessions to agents

Daemon creates a root context, cancelled on SIGTERM. Sessions derive
their context from it. Agent turns derive from session ctx. Tools
get the turn ctx.

Signal propagation:
- SIGTERM → daemonCancel() → all session ctxs cancelled → agent turns exit
- User interrupt → agent.Interrupt() (SIGINT equivalent, turn-level only)
- Session kill → sess.Cancel() → that session's agents stop

Previously sessions used context.Background() — daemon shutdown had
no automatic propagation to running agents.
This commit is contained in:
Levi Neely 2026-07-29 22:33:03 +02:00
parent bd30678ea0
commit d17b5df7dc
5 changed files with 15 additions and 4 deletions

View File

@ -178,7 +178,11 @@ func runServer(sockPath string) {
// Elevate broker (initialized after manager; closures capture the pointer).
var elevateBroker *elevate.Broker
daemonCtx, daemonCancel := context.WithCancel(context.Background())
defer daemonCancel()
mgr := mgr.NewManager(mgr.ManagerConfig{
Ctx: daemonCtx,
ToolRegistry: toolRegistry,
SkillsRegistry: skillsRegistry,
AgentsDir: agentsDirs[0],
@ -332,6 +336,7 @@ func runServer(sockPath string) {
<-sigChan
fmt.Println("shutting down")
daemonCancel() // signal all sessions via context propagation
if dbusAdapter != nil {
dbusAdapter.Close()
}

View File

@ -101,6 +101,7 @@ func newTestSessionManager(t *testing.T) *mgr.Manager {
t.Helper()
sink := testSink()
return mgr.NewManager(mgr.ManagerConfig{
Ctx: context.Background(),
Log: sink.NewLogger("test"),
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
@ -112,6 +113,7 @@ func newTestSessionManagerWithCore(t *testing.T) *mgr.Manager {
t.Helper()
sink := testSink()
return mgr.NewManager(mgr.ManagerConfig{
Ctx: context.Background(),
Log: sink.NewLogger("test"),
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
@ -347,6 +349,7 @@ func TestSessionManagerShutdown(t *testing.T) {
func TestSessionManagerRename(t *testing.T) {
sink := testSink()
s := mgr.NewManager(mgr.ManagerConfig{
Ctx: context.Background(),
Log: sink.NewLogger("test"),
Sink: sink,
ReadFile: func(string) ([]byte, error) { return nil, nil },
@ -1145,6 +1148,7 @@ func TestSessionManagerCreateSessionEnvExpansion(t *testing.T) {
t.Setenv("TEST_CWD", "/expanded/path")
sink := testSink()
s := mgr.NewManager(mgr.ManagerConfig{
Ctx: context.Background(),
Log: sink.NewLogger("test"),
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
@ -1169,6 +1173,7 @@ func TestSessionManagerCreateSessionEnvExpansion(t *testing.T) {
func TestSessionManagerCreateSessionTildeExpansion(t *testing.T) {
sink := testSink()
s := mgr.NewManager(mgr.ManagerConfig{
Ctx: context.Background(),
Log: sink.NewLogger("test"),
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },

View File

@ -137,7 +137,7 @@ func (s *Manager) CreateSession(args []string) (string, error) {
// FIXME: Eager dial means session creation blocks on SSH connect.
// Eventually make this async or move host info into the prompt
// scripts themselves (running remotely via the RPC).
rsrv, dialErr := execute.RemoteDial(context.Background(), execute.RemoteConfig{
rsrv, dialErr := execute.RemoteDial(s.cfg.Ctx, execute.RemoteConfig{
SSHTarget: remoteTarget,
CWD: cwd,
})
@ -228,7 +228,7 @@ func (s *Manager) CreateSession(args []string) (string, error) {
})
}
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(s.cfg.Ctx)
sess := NewSession(sessID, core, ctx, cancel)
sessPtr = sess
sess.remote = remoteTarget

View File

@ -28,6 +28,7 @@ func FileMode(name string) (os.FileMode, bool) {
// ManagerConfig holds the dependencies for a Manager.
type ManagerConfig struct {
Ctx context.Context // daemon context — sessions derive from this
AgentsDir string
SessionsDir string

View File

@ -137,7 +137,7 @@ func (s *Manager) restoreSession(ps *agent.PersistedAgent) error {
remoteTarget := ps.Remote
if remoteTarget != "" {
rsrv, dialErr := execute.RemoteDial(context.Background(), execute.RemoteConfig{
rsrv, dialErr := execute.RemoteDial(s.cfg.Ctx, execute.RemoteConfig{
SSHTarget: remoteTarget,
CWD: cwd,
})
@ -237,7 +237,7 @@ func (s *Manager) restoreSession(ps *agent.PersistedAgent) error {
},
})
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(s.cfg.Ctx)
sess := NewSession(sessID, core, ctx, cancel)
sessPtr = sess
sess.uname = uname