Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/eve/src/compiler/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ const compiledAgentConfigSchema: z.ZodType<CompiledAgentDefinition> = z
experimental: z
.object({
subagentPersistentSessions: z.boolean().optional(),
tasks: z.boolean().optional(),
workflow: compiledAgentWorkflowDefinitionSchema.optional(),
})
.strict()
Expand Down Expand Up @@ -812,6 +813,7 @@ export function createCompiledAgentNodeManifest(input: {
? undefined
: {
subagentPersistentSessions: input.config.experimental.subagentPersistentSessions,
tasks: input.config.experimental.tasks,
workflow:
input.config.experimental.workflow === undefined
? undefined
Expand Down
4 changes: 4 additions & 0 deletions packages/eve/src/compiler/normalize-agent-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ function normalizeExperimentalDefinition(
compiledExperimental.subagentPersistentSessions = experimental.subagentPersistentSessions;
}

if (experimental.tasks !== undefined) {
compiledExperimental.tasks = experimental.tasks;
}

if (experimental.workflow !== undefined) {
compiledExperimental.workflow = {
world: experimental.workflow.world,
Expand Down
47 changes: 47 additions & 0 deletions packages/eve/src/compiler/normalize-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,53 @@ describe("compileAgentManifest", () => {
);
});

it("rejects background-task configuration on subagents", async () => {
const subagentManifest = createAgentSourceManifest({
agentId: "research",
agentRoot: "/app/agent/subagents/research",
appRoot: "/app",
configModule: createModuleSourceRef({
logicalPath: "agent.ts",
}),
});
const manifest = createAgentSourceManifest({
agentId: "root",
agentRoot: "/app/agent",
appRoot: "/app",
subagents: [
createLocalSubagentSourceRef({
entryPath: "subagents/research/agent.ts",
logicalPath: "subagents/research",
manifest: subagentManifest,
rootPath: "/app/agent/subagents/research",
subagentId: "research",
}),
],
});

mocks.compileAgentConfig.mockImplementation(async (input: AgentSourceManifest) => {
if (input.agentId === "research") {
return createConfig({
description: "Research subagent",
name: "research",
experimental: {
tasks: true,
},
});
}

return createConfig({ name: "root" });
});
mocks.loadModuleBackedDefinition.mockResolvedValue({
description: "Research subagent",
model: "openai/gpt-5.5",
});

await expect(compileAgentManifest(manifest)).rejects.toThrow(
'Remove "experimental.tasks" from "research"',
);
});

it("compiles experimental Workflow tool configuration", async () => {
const manifest = createAgentSourceManifest({
agentId: "root",
Expand Down
9 changes: 7 additions & 2 deletions packages/eve/src/compiler/normalize-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,20 @@ async function compileAgentNodeManifest(
context: ManifestCompileContext,
options: {
readonly externalDependencies?: readonly string[];
readonly allowWorkflowConfig?: boolean;
readonly allowRootOnlyConfig?: boolean;
} = {},
): Promise<CompiledAgentNodeManifest> {
const rawConfig = await compileAgentConfig(manifest, context);
if (options.allowWorkflowConfig === false && rawConfig.experimental?.workflow !== undefined) {
if (options.allowRootOnlyConfig === false && rawConfig.experimental?.workflow !== undefined) {
throw new Error(
`Workflow runtime configuration is only supported on the root agent config. Remove "experimental.workflow" from "${manifest.agentId}".`,
);
}
if (options.allowRootOnlyConfig === false && rawConfig.experimental?.tasks !== undefined) {
throw new Error(
`Background tasks are only supported on the root agent config. Remove "experimental.tasks" from "${manifest.agentId}".`,
);
}
const externalDependencies = mergeExternalDependencies(
options.externalDependencies,
rawConfig.build?.externalDependencies,
Expand Down
4 changes: 2 additions & 2 deletions packages/eve/src/compiler/normalize-subagent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type CompileAgentNodeManifestFn = (
context: ManifestCompileContext,
options?: {
readonly externalDependencies?: readonly string[];
readonly allowWorkflowConfig?: boolean;
readonly allowRootOnlyConfig?: boolean;
},
) => Promise<CompiledAgentNodeManifest>;

Expand Down Expand Up @@ -171,7 +171,7 @@ async function compileSubagent(input: {
appRoot: input.appRoot,
},
input.context,
{ allowWorkflowConfig: false, externalDependencies: input.externalDependencies },
{ allowRootOnlyConfig: false, externalDependencies: input.externalDependencies },
);

const description = agent.config.description;
Expand Down
28 changes: 28 additions & 0 deletions packages/eve/src/internal/authored-definition/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,34 @@ describe("normalizeAgentDefinition", () => {
),
).toThrow('"experimental.subagentPersistentSessions" must be a boolean.');
});

it("accepts a boolean tasks flag", () => {
const definition = normalizeAgentDefinition(
{
model: "openai/gpt-5.5",
experimental: {
tasks: true,
},
},
FAILURE_MESSAGE,
);

expect(definition.experimental?.tasks).toBe(true);
});

it("rejects non-boolean tasks values", () => {
expect(() =>
normalizeAgentDefinition(
{
model: "openai/gpt-5.5",
experimental: {
tasks: "yes",
},
},
FAILURE_MESSAGE,
),
).toThrow('"experimental.tasks" must be a boolean.');
});
});

describe("normalizeScheduleDefinition", () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/eve/src/internal/authored-definition/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ function normalizeAgentExperimentalDefinition(
message: string,
): NonNullable<NormalizedAgentDefinition["experimental"]> {
const record = expectObjectRecord(value, message);
expectOnlyKnownKeys(record, ["subagentPersistentSessions", "workflow"], message);
expectOnlyKnownKeys(record, ["subagentPersistentSessions", "tasks", "workflow"], message);
const normalizedDefinition: Mutable<NonNullable<NormalizedAgentDefinition["experimental"]>> = {};

if (record.subagentPersistentSessions !== undefined) {
Expand All @@ -267,6 +267,13 @@ function normalizeAgentExperimentalDefinition(
normalizedDefinition.subagentPersistentSessions = record.subagentPersistentSessions;
}

if (record.tasks !== undefined) {
if (typeof record.tasks !== "boolean") {
throw new Error(`${message} "experimental.tasks" must be a boolean.`);
}
normalizedDefinition.tasks = record.tasks;
}

if (record.workflow !== undefined) {
normalizedDefinition.workflow = normalizeAgentWorkflowDefinition(record.workflow, message);
}
Expand Down
1 change: 1 addition & 0 deletions packages/eve/src/runtime/resolve-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ function createResolvedAgentConfig(manifest: CompiledAgentNodeManifest): Resolve
if (manifest.config.experimental !== undefined) {
config.experimental = {
subagentPersistentSessions: manifest.config.experimental.subagentPersistentSessions,
tasks: manifest.config.experimental.tasks,
workflow:
manifest.config.experimental.workflow === undefined
? undefined
Expand Down
7 changes: 7 additions & 0 deletions packages/eve/src/shared/agent-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ export interface AgentExperimentalDefinition {
* When unset, delegated children run as one-shot tasks.
*/
readonly subagentPersistentSessions?: boolean;
/**
* Runs this agent's delegated subagent calls as durable background tasks.
* The originating tool call returns a task receipt immediately and the
* model manages the work through the `task_*` framework tools. Implies
* persistent-session children for subagent dispatch. Root agents only.
*/
readonly tasks?: boolean;
/**
* Durable Workflow runtime configuration. Root agents may use this to select
* the Workflow world backing sessions and runs.
Expand Down
Loading