refactor(session): scope pi sessions to agents instead of tasks
Sessions were keyed by (task, agent). Every task switch started a fresh
transcript — the agent lost everything it had learned on the previous
task. That breaks the product premise: a CEO should remember its past
decisions, an engineer should carry context between bugs.
Move pi's AgentMessage transcript to be agent-scoped. One session per
agent (per company). Tasks enter the ongoing conversation as user turns;
rotation/compaction boundaries follow the agent's long-running session.
Additive schema only — no row rewrites on the live production DB.
- Migration v34: `ALTER TABLE agents ADD COLUMN session_id TEXT` +
`idx_agents_session_id`. `ensureOptionalColumns` mirror for fresh DBs.
- `AgentRecord.sessionId?: string | null` in the read model; included
in every full-agent SELECT (entity-crud, snapshot, agents-hierarchy
chain-of-command / direct-reports / heartbeat-eligible).
- `db.ensureAgentSession({ agentId, companyId, adapterType })` —
idempotent: returns existing `agents.session_id` if set, else inserts
a new `task_sessions` row with an empty `task_id` sentinel (agent-
scoped, task-id is now metadata) and stores the id on the agent.
- `db.setAgentSessionId(agentId, sessionId)` — used by rotation.
- `db.updateTasksActiveSessionForAgent(agentId, sessionId)` — mirrors
the agent's current session onto every task the agent owns, so
existing per-task readers (`tasks.active_session_id`) still work.
- `turn-context.resolveSession`: pulls the agent's session via
`ensureAgentSession` instead of `getActiveTaskSession(task.id)`,
then points the current task at it so UI continues to resolve.
- `session-management-service.rotateSession`: new session carries
`taskId:""`, then `setAgentSessionId` + `updateTasksActiveSessionForAgent`
so every task the agent owns follows the rotation boundary.
- `session-entries.listActive`: `ORDER BY created_at, sequence` — the
created_at timestamp is authoritative; sequence stays as same-ms
tiebreaker. Forward-safety for future merge scenarios.
Existing agents' first post-upgrade run creates a fresh agent-scoped
session. Their pre-migration per-task transcripts remain in the DB as
historical records but are not loaded for new runs. This is a one-time
transcript reset, accepted over a risky lossy merge on the live DB.
Documented in a code comment on `ensureAgentSession`.
This commit is contained in:
1 parent
5a3647e98e
commit
92795f7bfb
10 files changed
+127
-16
No files matched your search
@@ -127,11 +127,24 @@ function resolveEnabledConnectorModels(
|
||||
|
||||
function resolveSession(
|
||||
db: AppDatabase,
|
||||
agent: RunDispatchParams["agent"],
|
||||
task: RunDispatchParams["task"],
|
||||
companyId: string,
|
||||
connectorId: string,
|
||||
sessionHandoff: string | null,
|
||||
deps: Pick<TurnContextDeps, "evaluatePreRunSessionRotation" | "logger">,
|
||||
) {
|
||||
// Per-agent session: one ongoing transcript per agent; switching tasks
|
||||
// becomes a "now work on X" user turn rather than a fresh session.
|
||||
const agentSessionId = db.ensureAgentSession({
|
||||
agentId: agent.id,
|
||||
companyId,
|
||||
adapterType: connectorId,
|
||||
});
|
||||
// Mirror the agent's active session onto the task pointer so UI paths
|
||||
// that read per-task active_session_id keep working.
|
||||
db.updateTasksActiveSessionForAgent(agent.id, agentSessionId);
|
||||
|
||||
const activeSession = db.getActiveTaskSession(task.id);
|
||||
let taskSession: {
|
||||
id: string | null;
|
||||
@@ -145,7 +158,7 @@ function resolveSession(
|
||||
: null,
|
||||
sessionDisplayId: activeSession.sessionDisplayId,
|
||||
}
|
||||
: { id: null, ...db.getTaskSession(task.id) };
|
||||
: { id: agentSessionId, sessionParams: null, sessionDisplayId: null };
|
||||
|
||||
let finalHandoff = sessionHandoff;
|
||||
if (deps.evaluatePreRunSessionRotation && activeSession) {
|
||||
@@ -155,7 +168,7 @@ function resolveSession(
|
||||
if (!finalHandoff && rotationResult.handoff) {
|
||||
finalHandoff = rotationResult.handoff;
|
||||
}
|
||||
deps.logger.info(`[session] Pre-run compaction for task ${task.id}: session rotated before execution`);
|
||||
deps.logger.info(`[session] Pre-run compaction for agent ${agent.id}: session rotated before execution`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +273,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
const reviewTasks = db.listReviewTasksForManager(agent.id, companyId);
|
||||
const recentComments = assembleRecentComments(assignedTasks, reviewTasks, snapshot);
|
||||
const initialHandoff = resolveSessionHandoff(task.id, deps);
|
||||
const { taskSession, sessionHandoff } = resolveSession(db, task, connector.id, initialHandoff, deps);
|
||||
const { taskSession, sessionHandoff } = resolveSession(db, agent, task, companyId, connector.id, initialHandoff, deps);
|
||||
const skills = resolveSkills(companyId, agent.id, connector.id, agent.name, deps);
|
||||
|
||||
const systemPrompt = apiUrl ? buildTaskPrompt({
|
||||
|
||||
Reference in new issue
Block a user