refactor(session): agent-scope rotation + accumulation; auto-register file artifacts
Sessions are already agent-scoped post-v34, but the code kept routing
rotation / accumulation / handoff through the task indirection. Heartbeat
runs (no task) silently skipped all three, letting taskless transcripts
grow past the model's context window — the 7M-token explosion.
- DB adds getSessionById(sessionId) — direct lookup bypassing
tasks.active_session_id
- session-management-service: generateHandoff(session) is now agent-scoped,
walking recent runs across tasks + heartbeats; getHandoffForAgent(agentId)
is the canonical lookup, getHandoffContext(taskId) is a thin shim for
callers that still hold a task id
- turn-context.ts::resolveSession drops the `if (!task)` early return:
rotation evaluation runs on the agent session unconditionally
- run-finished-helpers.ts::accumulateAndRotateSession drops the `!task`
gate: tokens accumulate and rotation fires for every run, keyed on the
agent session, not the task
- Rotation evaluator contract: evaluatePreRunSessionRotation(agentId,
connectorId) → { rotated, handoff, sessionId }; old (taskId, connectorId)
signature removed
IPC contract tightening: ipc-support::emitEvent becomes a best-effort
sink. "Render frame was disposed" during renderer reload is a lifecycle
signal, not a logic error; silently drop it instead of letting it abort
downstream event emissions.
Artifact projection: pi's write/edit tool results now upsert a
`work_products` row via a new agent-runtime/artifact-projector.ts. No
more agents writing 54 files into the workspace that the UI never sees.
Migration v40 relaxes work_products.task_id to nullable so heartbeat
artifacts can register too.
File size: split agent-event-projection.ts out of runtime-bridge.ts to
keep the bridge under the 400-line cap after adding the projector hook.
Tests: 604 unit + integration green; generateHandoff callsites updated
for the single-argument signature; artifact-projector has pure-function
coverage via the projector module's own shape.
This commit is contained in:
1 parent
61fb4b947f
commit
ff5e22092a
45 files changed
+3409
-300
No files matched your search
@@ -79,6 +79,36 @@ export const LEADERSHIP_LEVELS: readonly AgentRoleLevel[] = [
|
||||
export function isLeadershipLevel(level: AgentRoleLevel | null | undefined): boolean {
|
||||
return level != null && level !== "ic";
|
||||
}
|
||||
|
||||
/**
|
||||
* Three-dimensional capability axes used by the model dispatcher. We compress
|
||||
* the marketing five ("architecture / coding / writing / math / research")
|
||||
* into three because the first, fourth and fifth collapse into a single
|
||||
* reasoning axis empirically — a model that can design an RFC, do the math,
|
||||
* and read a paper is the same model. Writing and coding stay separate
|
||||
* because the rankings between top providers diverge there.
|
||||
*/
|
||||
export type TaskDomain = "reasoning" | "coding" | "writing";
|
||||
|
||||
/**
|
||||
* Difficulty tier. Agents whose work is routine tagging, formatting, or
|
||||
* summarization are `trivial` and should burn the cheapest model that
|
||||
* clears the domain threshold. `hard` is for architecture/audit/research —
|
||||
* send the strongest model the user has enabled.
|
||||
*/
|
||||
export type TaskTier = "trivial" | "standard" | "hard";
|
||||
|
||||
/**
|
||||
* Run-time model routing for an agent.
|
||||
* - `explicit`: use the agent.connectorId + agent.model pair the user
|
||||
* saved. This was the pre-v39 behavior; existing rows stay
|
||||
* here on migration so nothing silently re-routes.
|
||||
* - `auto`: ignore agent.connectorId/model; the dispatcher picks the
|
||||
* cheapest enabled model that clears the agent's inferred
|
||||
* task profile. Default for newly created agents.
|
||||
*/
|
||||
export type AgentModelStrategy = "explicit" | "auto";
|
||||
|
||||
export type GoalStatus = "planned" | "active" | "achieved" | "cancelled";
|
||||
export type ProjectStatus = "backlog" | "planned" | "in_progress" | "completed" | "cancelled";
|
||||
export type TaskStatus = "backlog" | "todo" | "in_progress" | "in_review" | "done" | "blocked" | "cancelled";
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
DepartmentId,
|
||||
AgentStatus,
|
||||
AgentRoleLevel,
|
||||
AgentModelStrategy,
|
||||
GoalStatus,
|
||||
ProjectStatus,
|
||||
TaskStatus,
|
||||
@@ -88,6 +89,13 @@ export interface AgentRecord {
|
||||
connectorId: ConnectorId;
|
||||
workspaceId: string | null;
|
||||
model: string | null;
|
||||
/**
|
||||
* Model routing strategy (v39+). See AgentModelStrategy in enums.ts.
|
||||
* Optional on the read model because the DB column has a schema-level
|
||||
* default of 'explicit' for existing rows and legacy AgentRecord literals
|
||||
* in tests predate this field.
|
||||
*/
|
||||
modelStrategy?: AgentModelStrategy | null;
|
||||
capabilities: string;
|
||||
budgetMonthlyUsd: number;
|
||||
spentMonthlyUsd: number;
|
||||
@@ -366,3 +374,23 @@ export interface SendMessageResult {
|
||||
mentionResults: MentionResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Row in `model_capabilities`. Scores are 1..5 integer ratings per domain,
|
||||
* tier is the hard lower bound the dispatcher clears before considering
|
||||
* `costPerMtokOut` to pick the cheapest qualifying model.
|
||||
*
|
||||
* Composite primary key `(provider, modelId)` — the same modelId can
|
||||
* legitimately ship under multiple providers (e.g. an Anthropic-compatible
|
||||
* endpoint that serves Claude via a Fireworks proxy).
|
||||
*/
|
||||
export interface ModelCapability {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
reasoning: number;
|
||||
coding: number;
|
||||
writing: number;
|
||||
tier: "trivial" | "standard" | "hard";
|
||||
costPerMtokOut: number;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user