feat: agent session protocol — sessionId in result, continue support, frontmatter retry
Breaking changes:
- AgentRunResult now requires sessionId field
- AgentOptions now requires continue function
- Agent CLI outputs JSON {stepHash, sessionId} instead of plain CAS hash
- Engine parses JSON output (with legacy CAS hash fallback)
New features:
- Frontmatter validation retry: if agent output lacks valid frontmatter,
engine calls agent.continue() up to 2 times with correction message
- Session tracking: sessionId flows from agent → engine → StepOutput
- Hermes agent: session parse failure is now a hard error (no raw text fallback)
- Hermes agent: supports --resume for continue sessions
Closes #384
This commit is contained in:
@@ -624,7 +624,12 @@ function resolveAgentConfig(
|
||||
return agentConfig;
|
||||
}
|
||||
|
||||
function spawnAgent(agent: AgentConfig, threadId: ThreadId, role: string): CasRef {
|
||||
type SpawnAgentResult = {
|
||||
stepHash: CasRef;
|
||||
sessionId: string;
|
||||
};
|
||||
|
||||
function spawnAgent(agent: AgentConfig, threadId: ThreadId, role: string): SpawnAgentResult {
|
||||
const argv = [...agent.args, threadId, role];
|
||||
let stdout: string;
|
||||
try {
|
||||
@@ -646,10 +651,24 @@ function spawnAgent(agent: AgentConfig, threadId: ThreadId, role: string): CasRe
|
||||
}
|
||||
|
||||
const line = stdout.trim().split("\n").pop()?.trim() ?? "";
|
||||
if (!isCasRef(line)) {
|
||||
fail(`agent stdout is not a valid CAS hash: ${line || "(empty)"}`);
|
||||
|
||||
// Try JSON output first (new protocol)
|
||||
try {
|
||||
const parsed = JSON.parse(line) as Record<string, unknown>;
|
||||
const stepHash = parsed.stepHash;
|
||||
const sessionId = parsed.sessionId;
|
||||
if (typeof stepHash === "string" && isCasRef(stepHash) && typeof sessionId === "string") {
|
||||
return { stepHash, sessionId };
|
||||
}
|
||||
} catch {
|
||||
// Not JSON — fall through to legacy CAS hash parsing
|
||||
}
|
||||
return line;
|
||||
|
||||
// Legacy: plain CAS hash on stdout
|
||||
if (!isCasRef(line)) {
|
||||
fail(`agent stdout is not a valid CAS hash or JSON: ${line || "(empty)"}`);
|
||||
}
|
||||
return { stepHash: line, sessionId: "" };
|
||||
}
|
||||
|
||||
async function archiveThread(
|
||||
@@ -706,7 +725,7 @@ export async function cmdThreadStep(
|
||||
const agent = resolveAgentConfig(config, workflow, role, agentOverride);
|
||||
|
||||
loadDotenv({ path: getEnvPath(storageRoot) });
|
||||
const newHead = spawnAgent(agent, threadId, role);
|
||||
const { stepHash: newHead, sessionId } = spawnAgent(agent, threadId, role);
|
||||
|
||||
// Re-create store to pick up nodes written by the agent subprocess
|
||||
const uwfAfter = await createUwfStore(storageRoot);
|
||||
@@ -737,6 +756,7 @@ export async function cmdThreadStep(
|
||||
thread: threadId,
|
||||
head: newHead,
|
||||
done,
|
||||
sessionId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user