Compare commits

...

5 Commits

Author SHA1 Message Date
xiaoju 710d42d6b9 refactor(agent-kit): base AgentContext on ModeratorContext
AgentContext now extends ModeratorContext (start + steps) with threadId, role, store, and expanded workflow. Hermes and mock-agent read prompt/steps/systemPrompt from the new shape.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 16:14:13 +00:00
xiaomo 072d900fcb Merge pull request 'refactor: pass store via AgentContext, eliminate duplicate store instances' (#340) from refactor/pass-store-via-context into main 2026-05-18 16:05:38 +00:00
xiaoju cfebd07124 refactor(agent-kit): pass CAS store through AgentContext
Expose the store created during context build on AgentContext so agents
reuse the same in-memory cache instead of opening a second store.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 16:04:15 +00:00
xiaoju f2be6fc057 Merge pull request 'feat: hermes merkle detail — session turns as CAS tree (Phase 2 of #337)' (#339) from feat/337-agent-detail-merkle into main 2026-05-18 15:58:01 +00:00
xiaoju 2af8196451 Merge pull request 'feat: agent-kit interface change — agents own their detail (Phase 1 of #337)' (#338) from feat/337-agent-detail-merkle into main 2026-05-18 15:52:56 +00:00
5 changed files with 36 additions and 50 deletions
+10 -15
View File
@@ -1,12 +1,6 @@
import { spawn } from "node:child_process";
import {
type AgentContext,
type AgentRunResult,
createAgent,
createAgentStore,
resolveStorageRoot,
} from "@uncaged/uwf-agent-kit";
import { type AgentContext, type AgentRunResult, createAgent } from "@uncaged/uwf-agent-kit";
import {
loadHermesSession,
@@ -18,14 +12,14 @@ import {
const HERMES_COMMAND = "hermes";
const HERMES_MAX_TURNS = 90;
function buildHistorySummary(history: AgentContext["history"]): string {
if (history.length === 0) {
function buildHistorySummary(steps: AgentContext["steps"]): string {
if (steps.length === 0) {
return "";
}
const lines: string[] = ["## Previous Steps"];
for (let i = 0; i < history.length; i++) {
const step = history[i];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
if (step === undefined) {
continue;
}
@@ -39,8 +33,10 @@ function buildHistorySummary(history: AgentContext["history"]): string {
/** Assemble system prompt, task, and prior step outputs for Hermes. */
export function buildHermesPrompt(ctx: AgentContext): string {
const parts: string[] = [ctx.systemPrompt, "", "## Task", ctx.prompt];
const historyBlock = buildHistorySummary(ctx.history);
const roleDef = ctx.workflow.roles[ctx.role];
const systemPrompt = roleDef?.systemPrompt ?? "";
const parts: string[] = [systemPrompt, "", "## Task", ctx.start.prompt];
const historyBlock = buildHistorySummary(ctx.steps);
if (historyBlock !== "") {
parts.push("", historyBlock);
}
@@ -92,8 +88,7 @@ function spawnHermesChat(prompt: string): Promise<string> {
async function runHermes(ctx: AgentContext): Promise<AgentRunResult> {
const fullPrompt = buildHermesPrompt(ctx);
const rawOutput = await spawnHermesChat(fullPrompt);
const storageRoot = resolveStorageRoot();
const { store } = await createAgentStore(storageRoot);
const { store } = ctx;
const sessionId = parseSessionIdFromStdout(rawOutput);
if (sessionId !== null) {
+18 -16
View File
@@ -1,3 +1,4 @@
import type { Store } from "@uncaged/json-cas";
import type {
CasRef,
StartNodePayload,
@@ -6,6 +7,7 @@ import type {
ThreadId,
} from "@uncaged/uwf-protocol";
import { createAgentStore, loadThreadsIndex, resolveStorageRoot } from "./storage.js";
import type { AgentStore } from "./storage.js";
import type { AgentContext } from "./types.js";
type ChainState = {
@@ -20,8 +22,8 @@ function fail(message: string): never {
}
function walkChain(
store: Awaited<ReturnType<typeof createAgentStore>>["store"],
schemas: Awaited<ReturnType<typeof createAgentStore>>["schemas"],
store: Store,
schemas: AgentStore["schemas"],
headHash: CasRef,
): ChainState {
const headNode = store.get(headHash);
@@ -77,7 +79,7 @@ function walkChain(
}
function expandOutput(
store: Awaited<ReturnType<typeof createAgentStore>>["store"],
store: Store,
outputRef: CasRef,
): unknown {
const node = store.get(outputRef);
@@ -88,7 +90,7 @@ function expandOutput(
}
async function buildHistory(
store: Awaited<ReturnType<typeof createAgentStore>>["store"],
store: Store,
stepsNewestFirst: StepNodePayload[],
): Promise<StepContext[]> {
const chronological = [...stepsNewestFirst].reverse();
@@ -105,8 +107,8 @@ async function buildHistory(
}
async function loadWorkflow(
store: Awaited<ReturnType<typeof createAgentStore>>["store"],
schemas: Awaited<ReturnType<typeof createAgentStore>>["schemas"],
store: Store,
schemas: AgentStore["schemas"],
workflowRef: CasRef,
) {
const node = store.get(workflowRef);
@@ -141,22 +143,22 @@ export async function buildContext(threadId: ThreadId, role: string): Promise<Ag
fail(`unknown role "${role}" in workflow "${workflow.name}"`);
}
const history = await buildHistory(store, chain.stepsNewestFirst);
const steps = await buildHistory(store, chain.stepsNewestFirst);
return {
threadId,
role,
systemPrompt: roleDef.systemPrompt,
prompt: chain.start.prompt,
history,
start: chain.start,
steps,
workflow,
store,
};
}
export type BuildContextMeta = {
storageRoot: string;
store: Awaited<ReturnType<typeof createAgentStore>>["store"];
schemas: Awaited<ReturnType<typeof createAgentStore>>["schemas"];
store: Store;
schemas: AgentStore["schemas"];
headHash: CasRef;
chain: ChainState;
};
@@ -185,15 +187,15 @@ export async function buildContextWithMeta(
fail(`unknown role "${role}" in workflow "${workflow.name}"`);
}
const history = await buildHistory(store, chain.stepsNewestFirst);
const steps = await buildHistory(store, chain.stepsNewestFirst);
return {
threadId,
role,
systemPrompt: roleDef.systemPrompt,
prompt: chain.start.prompt,
history,
start: chain.start,
steps,
workflow,
store,
meta: { storageRoot, store, schemas, headHash, chain },
};
}
+1 -7
View File
@@ -7,11 +7,5 @@ export {
resolveModel,
} from "./extract.js";
export { createAgent } from "./run.js";
export {
createAgentStore,
getConfigPath,
getEnvPath,
loadWorkflowConfig,
resolveStorageRoot,
} from "./storage.js";
export { getConfigPath, getEnvPath, loadWorkflowConfig } from "./storage.js";
export type { AgentContext, AgentOptions, AgentRunFn, AgentRunResult } from "./types.js";
+4 -5
View File
@@ -1,11 +1,10 @@
import type { StepContext, ThreadId, WorkflowPayload } from "@uncaged/uwf-protocol";
import type { Store } from "@uncaged/json-cas";
import type { ModeratorContext, ThreadId, WorkflowPayload } from "@uncaged/uwf-protocol";
export type AgentContext = {
export type AgentContext = ModeratorContext & {
threadId: ThreadId;
role: string;
systemPrompt: string;
prompt: string;
history: StepContext[];
store: Store;
workflow: WorkflowPayload;
};
+3 -7
View File
@@ -1,11 +1,7 @@
#!/usr/bin/env bun
// Mock agent for smoke testing
import { bootstrap, type JSONSchema, putSchema } from "@uncaged/json-cas";
import {
createAgent,
createAgentStore,
resolveStorageRoot,
} from "../packages/uwf-agent-kit/src/index.js";
import { createAgent } from "../packages/uwf-agent-kit/src/index.js";
const MOCK_RAW_OUTPUT_SCHEMA: JSONSchema = {
title: "mock-raw-output",
@@ -20,8 +16,8 @@ const MOCK_RAW_OUTPUT_SCHEMA: JSONSchema = {
const agent = createAgent({
name: "mock",
run: async (ctx) => {
const output = `Mock output for role ${ctx.role}: task was "${ctx.prompt}"`;
const { store } = await createAgentStore(resolveStorageRoot());
const output = `Mock output for role ${ctx.role}: task was "${ctx.start.prompt}"`;
const { store } = ctx;
await bootstrap(store);
const schemaHash = await putSchema(store, MOCK_RAW_OUTPUT_SCHEMA);
const detailHash = await store.put(schemaHash, { text: output });