Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions mcp/src/graph/neo4j.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1664,15 +1664,6 @@ class Db {
}
}

async set_agent_session_summary(session_id: string, summary: string): Promise<void> {
const session = this.resilientSession();
try {
await session.run(Q.SET_AGENT_SESSION_SUMMARY_QUERY, { session_id, summary });
} finally {
await session.close();
}
}

async list_agent_sessions(): Promise<any[]> {
const session = this.resilientSession();
try {
Expand Down
6 changes: 0 additions & 6 deletions mcp/src/graph/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,6 @@ SET n.end_time = toInteger($end_time),
RETURN n
`;

export const SET_AGENT_SESSION_SUMMARY_QUERY = `
MATCH (n:AgentSession:${Data_Bank} {node_key: $session_id})
SET n.summary = $summary
RETURN n.node_key
`;

export const LIST_AGENT_SESSIONS_QUERY = `
MATCH (n:AgentSession)
WHERE n.file = 'session://generated'
Expand Down
81 changes: 3 additions & 78 deletions mcp/src/repo/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ import {
sessionExists,
saveSessionConfig,
saveSessionMetadata,
saveSessionSummary,
saveSummaryToGraph,
SessionConfig,
StepMeta,
} from "./session.js";
Expand Down Expand Up @@ -437,8 +435,6 @@ export interface GetContextOptions {
commitList?: string[];
// Skip prepending repo info to the first prompt (handler-level; recorded in config)
ignoreRepoInfo?: boolean;
summarize?: boolean;
summarizePrompt?: string;
// Replay mode: `prompt` is a full ModelMessage[] (including the system turn)
// that is run verbatim. No system prompt is generated, no enrichment blocks
// are appended, no session history is loaded, no attachments are resolved,
Expand Down Expand Up @@ -725,6 +721,7 @@ If the user's prompt mentions a sub-agent with an @mention (e.g. "@${validSubAge
model,
// Transparent replay: no code-generated system prompt — the system turn
// (if any) must be present inside the replayed messages array.
instructions: transparent ? undefined : instructions,
tools,
stopWhen,
stopSequences: ["[END_OF_ANSWER]"],
Expand Down Expand Up @@ -886,27 +883,14 @@ export async function get_context(
appendSearchProvenance(sessionId, provenanceCollector.entries);
}

const summaryUsage = opts.summarize
? await summarizeAfterTurn(
prepared,
newMessages,
opts.summarizePrompt || DEFAULT_SUMMARIZE_PROMPT,
).catch((e) => {
console.error("[summarize] failed (non-fatal):", e);
return undefined;
})
: undefined;

await appendSessionEnd(sessionId, {
end_time: new Date().toISOString(),
model: modelId,
provider,
duration_ms: duration,
status: "success",
token_usage: summaryUsage ? normalizeUsage(addUsage(usage, summaryUsage.usage)) : usage,
token_usage: usage,
});

if (summaryUsage?.summary) saveSummaryToGraph(sessionId, summaryUsage.summary);
}

const final = extractFinalAnswer(steps);
Expand Down Expand Up @@ -987,28 +971,14 @@ export async function stream_context(
const stepUsage = stepMetas.length > 0
? normalizeUsage(addUsage(...stepMetas.map((step) => step.usage)))
: normalizeUsage(usage);

const summaryUsage = opts.summarize
? await summarizeAfterTurn(
prepared,
newMessages,
opts.summarizePrompt || DEFAULT_SUMMARIZE_PROMPT,
).catch((e) => {
console.error("[summarize] failed (non-fatal):", e);
return undefined;
})
: undefined;

await appendSessionEnd(sessionId, {
end_time: new Date().toISOString(),
model: modelId,
provider,
duration_ms: duration,
status: "success",
token_usage: summaryUsage ? normalizeUsage(addUsage(stepUsage, summaryUsage.usage)) : stepUsage,
token_usage: stepUsage,
});

if (summaryUsage?.summary) saveSummaryToGraph(sessionId, summaryUsage.summary);
} catch (e) {
const aborted = isAbortError(e);
if (aborted) {
Expand All @@ -1029,51 +999,6 @@ export async function stream_context(
};
}

export const DEFAULT_SUMMARIZE_PROMPT = `Summarize this session for a future reader who was not here. Answer directly from the conversation above — do not use any tools.

Write prose under exactly these three headings:

## What happened
What was asked, what was explored, and what was concluded or delivered. Name the specific files, functions, commands, and decisions.

## Mistakes, wrong turns, and bad assumptions
This is the most important section. Record every incorrect assumption, every dead end, every search or tool call that returned nothing useful, every fix that had to be redone, and anything that behaved differently than expected. For each one, say what was believed and what turned out to be true. If the session ran clean, say so in one line.

## Open threads
Unfinished work, unanswered questions, and anything deliberately deferred.`;

async function summarizeAfterTurn(
prepared: PreparedAgent,
newMessages: ModelMessage[],
summarizePrompt: string,
): Promise<{ usage: ReturnType<typeof normalizeUsage>; summary: string } | undefined> {
const { sessionId } = prepared;
if (!sessionId) return;

const params = {
messages: [
...prepared.previousMessages,
...newMessages,
{ role: "user", content: summarizePrompt } as ModelMessage,
],
providerOptions: getProviderOptions(prepared.provider as any, undefined, prepared.modelId),
...(prepared.abortSignal ? { abortSignal: prepared.abortSignal } : {}),
};
const result = await prepared.agent.stream(params);

const summary = extractFinalAnswer((await result.steps) ?? []).answer.trim();
const usage = normalizeUsage(await result.totalUsage);
console.log("===> summarize_session", {
sessionId,
cache_read: usage.cache_read,
input: usage.input,
empty: !summary,
});

if (summary) saveSessionSummary(sessionId, { summary });
return { usage, summary };
}

/*
curl -X POST -H "Content-Type: application/json" -d '{"repo_url": "https://github.com/stakwork/hive", "prompt": "how does auth work in the repo"}' "http://localhost:3355/repo/agent"

Expand Down
26 changes: 4 additions & 22 deletions mcp/src/repo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { startTracking, endTracking } from "../busy.js";
import { services_agent } from "./services.js";
import { mocks_agent } from "./mocks.js";
import { ModelName } from "../aieo/src/index.js";
import { SessionConfig, loadSession, loadSessionConfig, loadSessionMetadata, loadSessionSummary, sessionExists } from "./session.js";
import { SessionConfig, loadSession, loadSessionConfig, loadSessionMetadata, sessionExists } from "./session.js";
import { McpServer } from "./mcpServers.js";
import { existsSync } from "fs";
import path from "path";
Expand All @@ -25,7 +25,6 @@ import {
abortRequest,
} from "./events.js";
import { db } from "../graph/neo4j.js";
import { isTrue } from "../graph/utils.js";

import { describe_nodes_agent, embed_nodes_agent } from "./descriptions.js";
export { services_agent, mocks_agent, describe_nodes_agent, embed_nodes_agent };
Expand Down Expand Up @@ -159,8 +158,6 @@ function parseAgentBody(req: Request) {
const maxTurns = typeof req.body.maxTurns === "number" ? req.body.maxTurns : undefined;
const headers = normalizeHeaders(req.body.headers);
const ignoreRepoInfo = req.body.ignoreRepoInfo as boolean | undefined;
const summarize = req.body.summarize as boolean | undefined;
const summarizePrompt = req.body.summarizePrompt as string | undefined;
// Stakwork API key for the run-research tools. Server-to-server secret
// (like `pat`/`apiKey`): read off the body, never exposed to the LLM.
const stakworkApiKey = req.body.stakworkApiKey as string | undefined;
Expand Down Expand Up @@ -203,7 +200,7 @@ function parseAgentBody(req: Request) {
repoUrl, username, pat, commitList, prompt, messages, toolsConfig, schema,
modelName, apiKey, baseUrl, logs, sessionId, sessionConfig, mcpServers,
systemOverride, mode, skills, subAgents, ggnn, stream, repoList, maxTurns, headers,
ignoreRepoInfo, attachments, _metadata, webhookUrl, summarize, summarizePrompt,
ignoreRepoInfo, attachments, _metadata, webhookUrl,
stakwork: stakworkApiKey ? { apiKey: stakworkApiKey, baseUrl: stakworkBaseUrl } : undefined,
googleSheets,
};
Expand Down Expand Up @@ -405,8 +402,6 @@ export async function repo_agent(req: Request, res: Response) {
_metadata: body._metadata,
commitList: body.commitList,
ignoreRepoInfo: body.ignoreRepoInfo,
summarize: body.summarize,
summarizePrompt: body.summarizePrompt,
},
);

Expand Down Expand Up @@ -533,8 +528,6 @@ export async function repo_agent(req: Request, res: Response) {
_metadata: body._metadata,
commitList: body.commitList,
ignoreRepoInfo: body.ignoreRepoInfo,
summarize: body.summarize,
summarizePrompt: body.summarizePrompt,
onStepEvent: (content) => {
const events = filterStepContent(content);
for (const ev of events) bus.emit(ev);
Expand Down Expand Up @@ -663,8 +656,7 @@ export async function get_agent_tools(req: Request, res: Response) {

export async function get_agent_session(req: Request, res: Response) {
const sessionId = req.query.session_id as string || req.query.sessionId as string;
const summarize = isTrue((req.query.summarize as string) || "");
console.log("===> GET /repo/agent/session", { hasSessionId: Boolean(sessionId), summarize });
console.log("===> GET /repo/agent/session", { hasSessionId: Boolean(sessionId) });

if (!sessionId) {
res.status(400).json({ error: "Missing session_id" });
Expand All @@ -680,17 +672,7 @@ export async function get_agent_session(req: Request, res: Response) {
const messages = loadSession(sessionId);
const config = loadSessionConfig(sessionId);
const _metadata = loadSessionMetadata(sessionId);
if (!summarize) {
res.json({ sessionId, messages, config, _metadata });
return;
}
res.json({
sessionId,
messages,
config,
_metadata,
summary: loadSessionSummary(sessionId)?.summary ?? null,
});
res.json({ sessionId, messages, config, _metadata });
} catch (e) {
console.error("Error in get_agent_session", e);
res.status(500).json({ error: "Internal server error" });
Expand Down
40 changes: 0 additions & 40 deletions mcp/src/repo/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ export interface SessionInitConfig {
ignoreRepoInfo?: boolean;
}

export interface SessionSummary {
summary: string;
}

export interface StepMeta {
step: number;
turn: number;
Expand Down Expand Up @@ -233,10 +229,6 @@ export function deleteSession(sessionId: string): void {
if (existsSync(metadataPath)) {
unlinkSync(metadataPath);
}
const summaryPath = getSummaryFile(sessionId);
if (existsSync(summaryPath)) {
unlinkSync(summaryPath);
}
deleteAttachments(sessionId);
}

Expand Down Expand Up @@ -308,36 +300,6 @@ export function loadSessionMetadata(sessionId: string): unknown | null {
}
}

function getSummaryFile(sessionId: string): string {
const sessionDir = path.isAbsolute(SESSIONS_DIR)
? SESSIONS_DIR
: path.join(process.cwd(), SESSIONS_DIR);
if (!existsSync(sessionDir)) {
mkdirSync(sessionDir, { recursive: true });
}
return path.join(sessionDir, `${sessionId}.summary.json`);
}

export function saveSessionSummary(sessionId: string, summary: SessionSummary): void {
writeFileSync(getSummaryFile(sessionId), JSON.stringify(summary, null, 2));
}

export function saveSummaryToGraph(sessionId: string, summary: string): void {
void db
?.set_agent_session_summary(sessionId, summary)
.catch((e: unknown) => console.error("[sessions] Neo4j summary write failed:", e));
}

export function loadSessionSummary(sessionId: string): SessionSummary | null {
const filePath = getSummaryFile(sessionId);
if (!existsSync(filePath)) return null;
try {
return JSON.parse(readFileSync(filePath, "utf-8")) as SessionSummary;
} catch {
return null;
}
}

/**
* Get the file path for a session's per-step metadata sidecar.
* This is separate from the conversation JSONL to avoid corrupting session replay.
Expand Down Expand Up @@ -582,8 +544,6 @@ export function pruneExpiredSessions(): number {
if (existsSync(annPath)) unlinkSync(annPath);
const configPath = filePath.replace(/\.jsonl$/, ".config.json");
if (existsSync(configPath)) unlinkSync(configPath);
const summaryPath = filePath.replace(/\.jsonl$/, ".summary.json");
if (existsSync(summaryPath)) unlinkSync(summaryPath);
deleteAttachments(sessionId);
pruned++;
}
Expand Down
Loading