Merge pull request 'feat: inject thread progress into agent prompt (#127)' (#128) from feat/127-inject-turn-count into main
CI / check (push) Successful in 1m44s

This commit was merged in pull request #128.
This commit is contained in:
2026-06-06 00:53:10 +00:00
6 changed files with 108 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
---
"@united-workforce/agent-hermes": patch
"@united-workforce/agent-claude-code": patch
"@united-workforce/util-agent": patch
---
feat: inject thread progress into agent prompt (#127)
Agents now receive a "Thread Progress" section in their prompt showing the
current step number and how many times the current role has spoken before.
This eliminates the need for agents to make tool calls (terminal, delegate_task)
just to count their own turn history.
@@ -7,6 +7,7 @@ import {
type AgentRunResult,
buildContinuationPrompt,
buildRolePrompt,
buildThreadProgress,
createAgent,
getCachedSessionId,
setCachedSessionId,
@@ -27,6 +28,10 @@ export function buildClaudeCodePrompt(ctx: AgentContext): string {
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
parts.push(ctx.outputFormatInstruction, "");
}
// Inject thread progress so the agent knows step count and role visit count
parts.push(buildThreadProgress(ctx.steps, ctx.role), "");
parts.push(rolePrompt, "", "## Task", ctx.start.prompt);
if (!ctx.isFirstVisit) {
+4
View File
@@ -6,6 +6,7 @@ import {
type AgentRunResult,
buildContinuationPrompt,
buildRolePrompt,
buildThreadProgress,
createAgent,
} from "@united-workforce/util-agent";
import type { AcpUsage } from "./acp-client.js";
@@ -60,6 +61,9 @@ export function buildHermesPrompt(ctx: AgentContext): string {
parts.push(ctx.outputFormatInstruction, "");
}
// Inject thread progress so the agent knows step count and role visit count
parts.push(buildThreadProgress(ctx.steps, ctx.role), "");
if (!ctx.isFirstVisit) {
// Re-entry: show only steps since last visit, meta only
parts.push(buildContinuationPrompt(ctx.steps, ctx.role, ctx.edgePrompt));
@@ -0,0 +1,59 @@
import type { StepContext } from "@united-workforce/protocol";
import { describe, expect, test } from "vitest";
import { buildThreadProgress } from "../src/build-thread-progress.js";
function makeStep(role: string): StepContext {
return {
role,
output: {},
detail: "0000000000000" as string,
agent: "uwf-mock",
edgePrompt: "",
startedAtMs: 0,
completedAtMs: 0,
cwd: "",
assembledPrompt: null,
usage: null,
content: null,
};
}
describe("buildThreadProgress", () => {
test("first step of thread", () => {
const result = buildThreadProgress([], "proponent");
expect(result).toContain("## Thread Progress");
expect(result).toContain("first step");
expect(result).toContain("first time");
expect(result).toContain("proponent");
});
test("second step, role not seen before", () => {
const steps = [makeStep("opponent")];
const result = buildThreadProgress(steps, "proponent");
expect(result).toContain("Thread step 2");
expect(result).toContain("spoken 0 times");
});
test("role has spoken once before", () => {
const steps = [makeStep("proponent"), makeStep("opponent")];
const result = buildThreadProgress(steps, "proponent");
expect(result).toContain("Thread step 3");
expect(result).toContain("spoken 1 time before");
// singular "time" not "times"
expect(result).not.toContain("1 times");
});
test("role has spoken multiple times", () => {
const steps = [
makeStep("proponent"),
makeStep("opponent"),
makeStep("proponent"),
makeStep("opponent"),
makeStep("proponent"),
makeStep("opponent"),
];
const result = buildThreadProgress(steps, "proponent");
expect(result).toContain("Thread step 7");
expect(result).toContain("spoken 3 times");
});
});
@@ -0,0 +1,27 @@
import type { StepContext } from "@united-workforce/protocol";
/**
* Build a compact thread-progress summary so the agent knows where it is
* in the conversation without making tool calls to count steps.
*
* Example output:
* ## Thread Progress
* Thread step 6. You (proponent) have spoken 2 times before this turn.
*/
export function buildThreadProgress(steps: StepContext[], role: string): string {
const totalSteps = steps.length;
const roleVisits = steps.filter((s) => s.role === role).length;
const parts = [`## Thread Progress`];
if (totalSteps === 0) {
parts.push(
`This is the first step of the thread. You (${role}) are speaking for the first time.`,
);
} else {
parts.push(
`Thread step ${totalSteps + 1}. You (${role}) have spoken ${roleVisits} time${roleVisits === 1 ? "" : "s"} before this turn.`,
);
}
return parts.join("\n");
}
+1
View File
@@ -1,6 +1,7 @@
export { buildContinuationPrompt } from "./build-continuation-prompt.js";
export { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
export { buildRolePrompt } from "./build-role-prompt.js";
export { buildThreadProgress } from "./build-thread-progress.js";
export type { BuildContextMeta } from "./context.js";
export { buildContext, buildContextWithMeta } from "./context.js";
export type { ExtractResult, ResolvedLlmProvider } from "./extract.js";