From a9b2d43f8408f57c67f7605ca4c10d903eb552cc Mon Sep 17 00:00:00 2001 From: Markov Date: Tue, 24 Feb 2026 11:18:00 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20lazy=20session=5Fid=20=E2=80=94=20genera?= =?UTF-8?q?ted=20on=20first=20agent=20invocation,=20not=20at=20startup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent.json | 8 ++++++-- src/config.ts | 55 ++++++++++++++++++++++++++++----------------------- src/router.ts | 12 ++++------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/agent.json b/agent.json index 42ca420..76141e3 100644 --- a/agent.json +++ b/agent.json @@ -9,8 +9,12 @@ "work_dir": "/root/projects/team-board", "model": "sonnet", "provider": "anthropic", - "capabilities": ["coding", "review"], + "capabilities": [ + "coding", + "review" + ], "max_concurrent_tasks": 2, "heartbeat_interval_sec": 30, - "allowed_paths": [] + "allowed_paths": [], + "session_id": "858c3419-e6a6-4487-940d-f28508784a49" } diff --git a/src/config.ts b/src/config.ts index 4aa2127..a23902a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -36,6 +36,33 @@ export interface AgentConfig { allowedPaths: string[]; /** Persistent session UUID — survives renames. Stored in agent.json. */ sessionId: string; + /** Internal: path to config file for lazy session_id write-back */ + _configPath: string | null; + /** Internal: parsed config data for write-back */ + _configData: Record; +} + +/** + * Ensure agent has a persistent session_id. + * Call lazily on first agent invocation, not at startup. + * Generates UUID and writes it back to agent.json. + */ +export function ensureSessionId(config: AgentConfig): string { + if (config.sessionId) return config.sessionId; + + const id = crypto.randomUUID(); + config.sessionId = id; + + if (config._configPath) { + try { + config._configData.session_id = id; + fs.writeFileSync(config._configPath, JSON.stringify(config._configData, null, 2) + '\n'); + } catch { + // Non-fatal + } + } + + return id; } const homeDir = os.homedir(); @@ -162,30 +189,8 @@ export function loadAgentConfig(): AgentConfig { apiKey: process.env.PICOGENT_API_KEY || process.env.ANTHROPIC_API_KEY || (file.api_key as string) || '', heartbeatIntervalSec: (file.heartbeat_interval_sec as number) || 30, allowedPaths: (file.allowed_paths as string[]) || [], - sessionId: ensureSessionId(file, configPath), + sessionId: (file.session_id as string) || '', + _configPath: configPath, + _configData: file, }; } - -/** - * Ensure agent.json has a persistent session_id. - * Generates UUID on first run and writes it back to the config file. - */ -function ensureSessionId(file: Record, configPath: string | null): string { - if (file.session_id && typeof file.session_id === 'string') { - return file.session_id; - } - - const id = crypto.randomUUID(); - - // Persist to agent.json if we have a path - if (configPath) { - try { - file.session_id = id; - fs.writeFileSync(configPath, JSON.stringify(file, null, 2) + '\n'); - } catch { - // Non-fatal — session works in memory - } - } - - return id; -} diff --git a/src/router.ts b/src/router.ts index cceec3d..a316a97 100644 --- a/src/router.ts +++ b/src/router.ts @@ -4,7 +4,7 @@ import { runAgent } from './agent.js'; import { TrackerClient } from './tracker/client.js'; import { createTrackerTools } from './tools/index.js'; import type { ToolDefinition } from '@mariozechner/pi-coding-agent'; -import type { AgentConfig } from './config.js'; +import { ensureSessionId, type AgentConfig } from './config.js'; import type { TrackerEvent, TrackerTask } from './tracker/types.js'; export interface TaskTracker { @@ -16,20 +16,16 @@ export class EventRouter { private log = logger.child({ component: 'event-router' }); private activeTasks = 0; private trackerTools: ToolDefinition[]; - /** Single session ID for the entire agent lifetime. */ - private readonly sessionId: string; - constructor( private config: AgentConfig, private client: TrackerClient, private taskTracker: TaskTracker, ) { - this.sessionId = config.sessionId; this.trackerTools = createTrackerTools({ trackerClient: client, agentSlug: config.slug, }); - this.log.info({ toolCount: this.trackerTools.length, sessionId: this.sessionId }, 'Tracker tools registered'); + this.log.info({ toolCount: this.trackerTools.length }, 'Tracker tools registered'); } async handleEvent(event: TrackerEvent): Promise { @@ -90,7 +86,7 @@ export class EventRouter { let collectedText = ''; for await (const msg of runAgent(prompt, { workDir: this.config.workDir, - sessionId: this.sessionId, + sessionId: ensureSessionId(this.config), model: this.config.model, provider: this.config.provider, systemPrompt: this.config.prompt || undefined, @@ -162,7 +158,7 @@ export class EventRouter { let collectedText = ''; for await (const msg of runAgent(content, { workDir: this.config.workDir, - sessionId: this.sessionId, + sessionId: ensureSessionId(this.config), model: this.config.model, provider: this.config.provider, systemPrompt: this.config.prompt || undefined,