Menu
popagent
publicLatest change dc4b371554df3a78c40c29085ead1db3de7e24f8 - Add model routing policy, companyStatus tool, Markdown channel output, compact Runtime settings by AkurAI Build
import { describe, expect, test } from "bun:test";
import type { Agent, IterationCompleteContext } from "@mastra/core/agent";
import {
type AgentRuntimeSettings,
type AgentExecutionSource,
type AgentTask,
type AgentTaskProgress,
type MemorySettings,
} from "./api-types";
import { agentActivity, AgentExecutionRuntime, conversationWithoutUnresolvedTail, createAgentTaskExecutor, drainLiveActivity, iterationActivity, LIVE_ACTIVITY_INTERVAL_MS, observationalMemoryConfig } from "./agent-execution";
import { HookRuntime, type HookEvent } from "./hooks";
import type { SelfUpdateRun } from "./self-update-policy";
import { TaskAgentCommunicationSession } from "./task-agent-communication";
import { createAgentRequestContext } from "./agent-context";
import type { TaskAgentCommunicationMessage } from "./task-agent-communication";
const memoryConfiguration: MemorySettings = {
autoCompact: false,
observationTokens: 30_000,
reflectionTokens: 40_000,
recentMessagePercent: 20,
asyncBuffering: true,
bufferIntervalPercent: 20,
bufferOnIdle: true,
observationBlockPercent: 120,
reflectionBufferPercent: 50,
reflectionBlockPercent: 120,
optimizeObserverContext: true,
previousObserverTokens: 2_000,
retrievalEnabled: true,
retrievalScope: "resource",
temporalMarkers: false,
activateAfterIdle: "off",
activateOnProviderChange: false,
shareTokenBudget: false,
observeAttachments: "auto",
observationInstruction: "",
reflectionInstruction: "",
internalRecall: true,
internalRetention: true,
updatedAt: "",
};
const runtimeConfiguration: AgentRuntimeSettings = {
defaultModel: "test/default",
modelSource: "configured",
modelRouting: { enabled: false, strategy: "fallback", cooldownMs: 900_000, routes: [] },
supervisorMaxSteps: 9,
specialistMaxSteps: 7,
toolConcurrency: 2,
delegationContextMessages: 5,
delegationResultCharacters: 4_000,
maxProcessorRetries: 4,
finalResponseFeedback: "Return the configured final response",
delegationFailureFeedback: "Delegate {{agentId}} failed: {{error}}",
delegationResultTruncationMarker: "\n[truncated]",
taskConcurrency: 2,
taskPollIntervalMs: 1_000,
taskTimeoutMs: 30_000,
taskStaleAfterMs: 60_000,
updatedAt: "",
};
describe("AgentExecutionRuntime", () => {
test("maps persisted advanced observational-memory settings into Mastra config", () => {
const config = observationalMemoryConfig({
...memoryConfiguration,
autoCompact: true,
recentMessagePercent: 25,
bufferIntervalPercent: 30,
observationBlockPercent: 135,
reflectionBufferPercent: 60,
reflectionBlockPercent: 145,
previousObserverTokens: 3_500,
retrievalScope: "thread",
temporalMarkers: true,
activateAfterIdle: "auto",
activateOnProviderChange: true,
observeAttachments: "none",
observationInstruction: " Keep decisions. ",
reflectionInstruction: " Preserve blockers. ",
}, "test/default");
expect(config).toEqual({
observationalMemory: expect.objectContaining({
enabled: true,
scope: "thread",
activateAfterIdle: "auto",
activateOnProviderChange: true,
temporalMarkers: true,
retrieval: expect.objectContaining({ scope: "thread" }),
observation: {
messageTokens: 30_000,
bufferTokens: 0.3,
bufferActivation: 0.75,
bufferOnIdle: true,
blockAfter: 1.35,
previousObserverTokens: 3_500,
observeAttachments: false,
instruction: "Keep decisions.",
},
reflection: {
observationTokens: 40_000,
bufferActivation: 0.6,
blockAfter: 1.45,
instruction: "Preserve blockers.",
},
}),
});
});
test("disables async buffering when token budgets are shared", () => {
const config = observationalMemoryConfig({
...memoryConfiguration,
autoCompact: true,
shareTokenBudget: true,
}, "test/default");
expect(config.observationalMemory.observation).toEqual(expect.objectContaining({
bufferTokens: false,
bufferOnIdle: false,
}));
expect(config.observationalMemory.reflection).not.toHaveProperty("bufferActivation");
});
test("gives background tasks the chat execution context", async () => {
const events: HookEvent[] = [];
const episodes: Array<{ sessionId: string; userText: string; assistantText: string }> = [];
const progressUpdates: AgentTaskProgress[] = [];
let appliedAgent: Agent | undefined;
let streamedPrompt: string | undefined;
let streamedOptions: Record<string, unknown> | undefined;
const hooks = new HookRuntime({
config: {
version: 1,
hooks: {
UserPromptSubmit: [{ id: "normalize", type: "http", url: "https://hooks.example/normalize" }],
},
},
transport: async (_handler, event) => {
events.push(event);
return {
schemaVersion: 1,
outcome: "modify",
reason: "normalize",
replacement: "normalized task prompt",
additionalContext: "managed hook context",
};
},
});
const agent = {
stream: async (prompt: string, options: Record<string, unknown>) => {
streamedPrompt = prompt;
streamedOptions = options;
await (options.onIterationComplete as (
context: IterationCompleteContext,
) => Promise<unknown>)({
iteration: 2,
maxIterations: runtimeConfiguration.supervisorMaxSteps,
text: "",
toolCalls: [{ id: "tool-1", name: "webSearch", args: {} }],
toolResults: [],
isFinal: false,
finishReason: "tool-calls",
runId: "run-1",
agentId: "orchistrator",
agentName: "orchistrator",
messages: [],
});
const observeSpecialist = (
options.requestContext as { get: (key: string) => unknown }
).get("popagent.iterationObserver") as (context: IterationCompleteContext) => Promise<void>;
await observeSpecialist({
iteration: 4,
maxIterations: 12,
text: "",
toolCalls: [{ id: "tool-2", name: "webSearch", args: {} }],
toolResults: [],
isFinal: false,
finishReason: "tool-calls",
runId: "run-2",
agentId: "researcher",
agentName: "Researcher",
messages: [],
});
await (options.onFinish as (result: { text: string }) => Promise<void>)({ text: "task result" });
return { text: Promise.resolve("task result") };
},
} as unknown as Agent;
const execution = new AgentExecutionRuntime(
agent,
hooks,
{
formatRecall: async ({ resourceId, query }) => {
expect(resourceId).toBe("popagent-user");
expect(query).toBe("normalized task prompt");
return "recalled durable fact";
},
retainEpisode: async ({ sessionId, userText, assistantText }) => {
episodes.push({ sessionId, userText, assistantText });
},
},
{
apply: async (candidate) => { appliedAgent = candidate; },
},
{
get: async () => memoryConfiguration,
},
{
get: async () => runtimeConfiguration,
},
);
const task: AgentTask = {
id: "task-1",
sessionId: "session-1",
scheduleId: null,
source: "user",
workspaceId: "default",
prompt: "original task prompt",
model: runtimeConfiguration.defaultModel,
status: "queued",
output: null,
error: null,
stepsCompleted: 0,
progress: null,
recoveryCount: 0,
attemptCount: 0,
maxAttempts: 3,
nextAttemptAt: null,
lastErrorClass: null,
deadLetteredAt: null,
createdAt: "",
startedAt: null,
completedAt: null,
};
const controller = new AbortController();
const traceId = "a".repeat(32);
let communicationSession!: TaskAgentCommunicationSession;
const output = await createAgentTaskExecutor(agent, execution, undefined, (claimedTask, signal) => {
communicationSession = new TaskAgentCommunicationSession({
taskId: claimedTask.id,
workspaceId: claimedTask.workspaceId,
resourceId: "popagent-user",
participants: ["orchistrator", "researcher"],
signal,
});
return communicationSession;
})(
task,
controller.signal,
"turn-1",
traceId,
async (progress) => { progressUpdates.push(progress); },
);
expect(output).toBe("task result");
expect(progressUpdates).toEqual([
expect.objectContaining({
stepsCompleted: 2,
progress: "Using webSearch",
activity: expect.objectContaining({ agentName: "orchistrator", tools: ["webSearch"] }),
}),
expect.objectContaining({
stepsCompleted: 4,
progress: "Researcher: Using webSearch",
activity: expect.objectContaining({ agentName: "Researcher", tools: ["webSearch"] }),
}),
]);
expect(streamedPrompt).toBe("normalized task prompt");
expect(appliedAgent).toBe(agent);
expect(events).toEqual([expect.objectContaining({
eventName: "UserPromptSubmit",
sessionId: "session-1",
detail: { prompt: "original task prompt" },
})]);
expect(streamedOptions).toEqual(expect.objectContaining({
abortSignal: controller.signal,
memory: { thread: "session-1", resource: "popagent-user" },
hooks: expect.objectContaining({
beforeToolCall: expect.any(Function),
afterToolCall: expect.any(Function),
}),
delegation: expect.objectContaining({
onDelegationStart: expect.any(Function),
onDelegationComplete: expect.any(Function),
}),
outputProcessors: [expect.anything()],
errorProcessors: [expect.anything()],
memoryConfig: { observationalMemory: { enabled: false } },
maxSteps: runtimeConfiguration.supervisorMaxSteps,
toolCallConcurrency: { limit: runtimeConfiguration.toolConcurrency, strategy: "called" },
tracingOptions: expect.objectContaining({
traceId,
tags: ["task"],
metadata: expect.objectContaining({
sessionId: "session-1",
turnId: "turn-1",
workspaceId: "default",
executionSource: "task",
taskId: "task-1",
}),
}),
}));
const streamedRequestContext = streamedOptions?.requestContext;
if (
!streamedRequestContext
|| typeof streamedRequestContext !== "object"
|| !("get" in streamedRequestContext)
|| typeof streamedRequestContext.get !== "function"
) {
throw new Error("Expected a Mastra request context");
}
expect(streamedRequestContext.get("popagent.sessionId")).toBe("session-1");
expect(streamedRequestContext.get("popagent.model")).toBe(runtimeConfiguration.defaultModel);
expect(streamedRequestContext.get("popagent.executionSource")).toBe("task");
expect(streamedRequestContext.get("popagent.taskId")).toBe("task-1");
expect(streamedRequestContext.get("popagent.communicationSession")).toBe(communicationSession);
expect(await communicationSession.send({
from: "orchistrator",
to: "researcher",
body: "late",
})).toMatchObject({ outcome: "failed", error: expect.stringContaining("closed") });
expect(streamedOptions?.context).toEqual([
{ role: "user", content: "<memory-context>\nmanaged hook context\n</memory-context>" },
{ role: "user", content: "<memory-context>\nrecalled durable fact\n</memory-context>" },
]);
expect(episodes).toEqual([{
sessionId: "session-1",
userText: "normalized task prompt",
assistantText: "task result",
}]);
});
test("runs an idle addressed participant and records its identity-bound reply", async () => {
const persisted: TaskAgentCommunicationMessage[] = [];
const reviewerPrompts: string[] = [];
let communicationSession!: TaskAgentCommunicationSession;
const reviewer = {
__fork() { return this; },
setBrowser() {},
async stream(prompt: string) {
reviewerPrompts.push(prompt);
return { text: Promise.resolve("") };
},
} as unknown as Agent;
const main = {
async stream() {
const receipt = await communicationSession.send({
from: "implementer",
to: "reviewer",
body: "Review the completed change.",
});
expect(receipt.outcome).toBe("woken");
return { text: Promise.resolve("Main task complete.") };
},
} as unknown as Agent;
const execution = {
async prepare(input: {
communicationSession?: TaskAgentCommunicationSession;
executionSource?: AgentExecutionSource;
selfUpdateWorkspacePath?: string;
prompt: string;
}) {
input.communicationSession?.configureExecution({
executionSource: input.executionSource ?? "task",
...(input.selfUpdateWorkspacePath
? { selfUpdateWorkspacePath: input.selfUpdateWorkspacePath }
: {}),
});
return {
prompt: input.prompt,
options: {
requestContext: createAgentRequestContext({
resourceId: "popagent-user",
sessionId: "task:wake",
turnId: "turn-wake",
model: runtimeConfiguration.defaultModel,
workspaceId: "default",
executionSource: input.executionSource ?? "task",
runtimeSettings: runtimeConfiguration,
communicationSession: input.communicationSession,
}),
},
};
},
} as unknown as AgentExecutionRuntime;
const task: AgentTask = {
id: "task-wake",
sessionId: null,
scheduleId: null,
source: "user",
workspaceId: "default",
prompt: "Implement and review",
model: runtimeConfiguration.defaultModel,
status: "running",
output: null,
error: null,
stepsCompleted: 0,
progress: null,
recoveryCount: 0,
attemptCount: 1,
maxAttempts: 3,
nextAttemptAt: null,
lastErrorClass: null,
deadLetteredAt: null,
createdAt: "",
startedAt: "",
completedAt: null,
};
const execute = createAgentTaskExecutor(
main,
execution,
undefined,
(claimedTask, signal, wake) => {
communicationSession = new TaskAgentCommunicationSession({
taskId: claimedTask.id,
workspaceId: claimedTask.workspaceId,
resourceId: "popagent-user",
participants: ["implementer", "reviewer"],
signal,
wake,
persist: async (message) => { persisted.push(message); },
});
return communicationSession;
},
{ reviewer },
);
expect(await execute(
task,
new AbortController().signal,
"turn-wake",
"d".repeat(32),
async () => undefined,
)).toBe("Main task complete.");
expect(reviewerPrompts).toEqual([
expect.stringContaining('Reply as yourself to "implementer"'),
]);
expect(persisted).toEqual([
expect.objectContaining({ from: "implementer", to: "reviewer" }),
expect.objectContaining({
from: "reviewer",
to: "implementer",
body: "No additional findings.",
replyTo: persisted[0]?.id,
}),
]);
});
test("honors internal recall and retention settings for background tasks", async () => {
let recallCalls = 0;
const episodes: string[] = [];
const runtime = new AgentExecutionRuntime(
{ } as Agent,
{ dispatch: async () => ({ additionalContext: [] }) } as unknown as HookRuntime,
{
formatRecall: async () => { recallCalls++; return "unexpected"; },
retainEpisode: async () => { episodes.push("retained"); },
},
{ apply: async () => {} },
{ get: async () => ({ ...memoryConfiguration, internalRecall: false, internalRetention: false }) },
{ get: async () => runtimeConfiguration },
);
const prepared = await runtime.prepare({
sessionId: "internal-session",
turnId: "turn-1",
model: runtimeConfiguration.defaultModel,
workspaceId: "default",
executionSource: "task",
prompt: "background work",
traceId: "b".repeat(32),
});
await prepared.options.onFinish({ text: "done" });
expect(recallCalls).toBe(0);
expect(prepared.options.context).toEqual([]);
expect(episodes).toEqual([]);
});
test("injects deterministic repository evidence before the model runs", async () => {
const runtime = new AgentExecutionRuntime(
{ } as Agent,
{ dispatch: async () => ({ additionalContext: [] }) } as unknown as HookRuntime,
{ formatRecall: async () => "", retainEpisode: async () => {} },
{ apply: async () => {} },
{ get: async () => memoryConfiguration },
{ get: async () => runtimeConfiguration },
{
build: async () => ({
repository: { clean: true, changedFiles: [] },
candidates: [{ path: "src/payment.ts", score: 60, symbols: [] }],
tests: ["bun test src/payment.test.ts"],
readyForImplementation: true,
gaps: [],
}),
},
);
const prepared = await runtime.prepare({
sessionId: "session-1",
turnId: "turn-1",
model: runtimeConfiguration.defaultModel,
workspaceId: "default",
executionSource: "chat",
prompt: "Fix payment",
traceId: "c".repeat(32),
});
expect(prepared.options.context).toEqual(expect.arrayContaining([
expect.objectContaining({ content: expect.stringContaining("<repository-brief>") }),
]));
expect((prepared.options.requestContext as { get: (key: string) => unknown })
.get("popagent.repositoryBriefReady")).toBe(true);
});
test("derives trusted self-update execution source before ordinary schedule lineage", async () => {
const prepared: Array<Record<string, unknown>> = [];
const accepted: string[] = [];
const run = {
taskId: "self-update-task",
workspaceId: "default",
repositoryPath: "/registered",
path: "/contained/self-update",
branch: "autonomous/self-update/test",
snapshot: { head: "a".repeat(40), branch: "main", remotes: "remote-hash" },
cloneRemotes: "clone-remote-hash",
delegations: new Set<string>(),
deploymentEnabled: false,
} satisfies SelfUpdateRun;
const execution = {
async prepare(input: Record<string, unknown>) {
prepared.push(input);
return { prompt: input.prompt as string, options: {} };
},
} as unknown as AgentExecutionRuntime;
const agent = {
__fork() { return this; },
setBrowser() {},
async stream() { return { text: Promise.resolve("done") }; },
} as unknown as Agent;
const policy = {
async begin() { return run; },
observeDelegation() {},
async accept(candidate: SelfUpdateRun) { accepted.push(candidate.taskId); },
async reject() {},
};
const baseTask: AgentTask = {
id: "scheduled-user-task",
sessionId: null,
scheduleId: "user-schedule",
source: "user",
workspaceId: "default",
prompt: "scheduled",
model: "test/default",
status: "running",
output: null,
error: null,
stepsCompleted: 0,
progress: null,
recoveryCount: 0,
attemptCount: 1,
maxAttempts: 3,
nextAttemptAt: null,
lastErrorClass: null,
deadLetteredAt: null,
createdAt: "",
startedAt: "",
completedAt: null,
};
const executeTask = createAgentTaskExecutor(agent, execution, policy);
await executeTask(baseTask, new AbortController().signal, "turn-user", "b".repeat(32), async () => undefined);
await executeTask(
{ ...baseTask, id: run.taskId, source: "self-update" },
new AbortController().signal,
"turn-self-update",
"c".repeat(32),
async () => undefined,
);
expect(prepared.map((input) => input.executionSource)).toEqual(["schedule", "self-update"]);
expect(prepared[1]).toEqual(expect.objectContaining({
selfUpdateWorkspacePath: run.path,
scheduleId: "user-schedule",
}));
expect(accepted).toEqual([run.taskId]);
});
test("describes workspace file reads without exposing internal tool names", () => {
expect(iterationActivity({
iteration: 1,
maxIterations: 12,
text: "",
toolCalls: [{
id: "tool-1",
name: "mastra_workspace_read_file",
args: { path: "src/agent.ts" },
}],
toolResults: [],
isFinal: false,
finishReason: "tool-calls",
runId: "run-1",
agentId: "researcher",
agentName: "Researcher",
messages: [],
})).toBe("Researcher: Reading File: src/agent.ts");
});
test("serializes specialist iterations for live chat activity", () => {
const activity = agentActivity("turn-7", {
iteration: 3,
maxIterations: 12,
text: "Verified the repository contract.",
toolCalls: [
{ id: "tool-1", name: "read", args: { path: "src" } },
{ id: "tool-2", name: "read", args: { path: "test" } },
],
toolResults: [
{ id: "tool-1", name: "read", result: { files: ["src/agent.ts"] } },
{ id: "tool-2", name: "read", result: null, error: new Error("Path denied") },
],
isFinal: true,
finishReason: "stop",
runId: "run-7",
agentId: "reviewer",
agentName: "Reviewer",
messages: [],
});
expect(activity).toEqual({
turnId: "turn-7",
runId: "run-7",
agentId: "reviewer",
agentName: "Reviewer",
iteration: 3,
maxIterations: 12,
isFinal: true,
finishReason: "stop",
text: "Verified the repository contract.",
tools: ["read"],
toolCalls: [
{
id: "tool-1",
name: "read",
args: { path: "src" },
result: { files: ["src/agent.ts"] },
status: "complete",
},
{
id: "tool-2",
name: "read",
args: { path: "test" },
result: null,
error: "Path denied",
status: "error",
},
],
});
});
});
describe("conversationWithoutUnresolvedTail", () => {
test("drops the unresolved assistant tail left by an exhausted step budget", () => {
const messages = [
{ role: "user", content: "prepare the report" },
{ role: "assistant", content: [{ type: "tool-call", toolCallId: "call-1" }] },
{ role: "tool", content: [{ type: "tool-result", toolCallId: "call-1" }] },
{ role: "assistant", content: [{ type: "tool-call", toolCallId: "call-2" }] },
];
expect(conversationWithoutUnresolvedTail(messages)).toEqual(messages.slice(0, 3));
});
test("keeps a conversation that already ends on resolved evidence", () => {
const messages = [
{ role: "user", content: "prepare the report" },
{ role: "tool", content: [{ type: "tool-result", toolCallId: "call-1" }] },
];
expect(conversationWithoutUnresolvedTail(messages)).toEqual(messages);
});
});
describe("drainLiveActivity", () => {
const base = { turnId: "turn-1", agentId: "orchistrator", agentName: "Orchistrator", maxIterations: 12 };
async function* chunks(items: { type: string; payload?: unknown }[]) {
for (const item of items) yield item;
}
test("reports a tool call before the iteration finishes", async () => {
const seen: { iteration: number; tools: string[]; text: string }[] = [];
await drainLiveActivity(chunks([
{ type: "step-start", payload: {} },
{ type: "tool-call", payload: { toolName: "skill", toolCallId: "call-1" } },
{ type: "tool-call", payload: { toolName: "skill", toolCallId: "call-2" } },
{ type: "tool-call", payload: { toolName: "codeContext", toolCallId: "call-3" } },
]), {
...base,
now: () => 0,
report: (activity) => { seen.push({ iteration: activity.iteration, tools: activity.tools, text: activity.text }); },
});
expect(seen.map((entry) => entry.tools)).toEqual([["skill"], ["skill", "codeContext"]]);
expect(seen.every((entry) => entry.iteration === 1)).toBe(true);
});
test("throttles streamed text and resets state on the next step", async () => {
const seen: string[] = [];
let clock = 0;
await drainLiveActivity(chunks([
{ type: "step-start", payload: {} },
{ type: "text-delta", payload: { text: "Prep" } },
{ type: "text-delta", payload: { text: "aring" } },
{ type: "step-start", payload: {} },
{ type: "text-delta", payload: { text: "Second" } },
]), {
...base,
now: () => (clock += LIVE_ACTIVITY_INTERVAL_MS),
report: (activity) => { seen.push(activity.text); },
});
expect(seen).toEqual(["Prep", "Preparing", "Second"]);
});
});