Skip to content

Commit 0b84f29

Browse files
committed
feat(opencode): task signals — structured returns, terse completion, sparse context, wake-on-message
Four controls over what flows in and out of a Task dispatch, all defaulting to today's behavior. task_return lets a subagent set a free-form JSON result on its own session row, delivered in the completion frame and on the task.completed event, so a coordinator can read a machine result instead of parsing prose. Terse completion replaces a background child's full completion body with a digest plus a pointer to the child session, which stops large returns from rewriting the parent's cache tail on every completion. Sparse dispatch context drops global instruction files, the skills block, and the MCP block, and skips assembling them rather than discarding the output. Wake-on-message wakes an idle child to drain its inbox when a sibling or coordinator message lands, budgeted per run and never waking a child whose ancestor chain is gone. Terse and sparse read from a task config family with precedence global → project → agent frontmatter → dispatch param.
1 parent 50e2f8d commit 0b84f29

34 files changed

Lines changed: 1773 additions & 48 deletions

packages/core/schema.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"version": "7",
33
"dialect": "sqlite",
4-
"id": "d3f3a5db-0f01-49de-827d-cce528d30460",
4+
"id": "e6ecea79-5939-40bd-9e2d-606016753258",
55
"prevIds": [
6-
"f14a9b18-8207-487e-a3d3-227e629ba9ad"
6+
"b1dd5863-f1ea-4f16-8aac-0134c692891c"
77
],
88
"ddl": [
99
{
@@ -1384,6 +1384,16 @@
13841384
"entityType": "columns",
13851385
"table": "session"
13861386
},
1387+
{
1388+
"type": "text",
1389+
"notNull": false,
1390+
"autoincrement": false,
1391+
"default": null,
1392+
"generated": null,
1393+
"name": "result",
1394+
"entityType": "columns",
1395+
"table": "session"
1396+
},
13871397
{
13881398
"type": "real",
13891399
"notNull": true,
@@ -1524,6 +1534,16 @@
15241534
"entityType": "columns",
15251535
"table": "session"
15261536
},
1537+
{
1538+
"type": "text",
1539+
"notNull": false,
1540+
"autoincrement": false,
1541+
"default": null,
1542+
"generated": null,
1543+
"name": "context_mode",
1544+
"entityType": "columns",
1545+
"table": "session"
1546+
},
15271547
{
15281548
"type": "text",
15291549
"notNull": true,

packages/core/src/database/migration.gen.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Effect } from "effect"
2+
import type { DatabaseMigration } from "../migration"
3+
4+
export default {
5+
id: "20260705045947_productive_masque",
6+
up(tx) {
7+
return Effect.gen(function* () {
8+
yield* tx.run(`ALTER TABLE \`session\` ADD \`result\` text;`)
9+
})
10+
},
11+
} satisfies DatabaseMigration.Migration
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Effect } from "effect"
2+
import type { DatabaseMigration } from "../migration"
3+
4+
export default {
5+
id: "20260705061319_silly_the_hood",
6+
up(tx) {
7+
return Effect.gen(function* () {
8+
yield* tx.run(`ALTER TABLE \`session\` ADD \`context_mode\` text;`)
9+
})
10+
},
11+
} satisfies DatabaseMigration.Migration

packages/core/src/database/schema.gen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export default {
224224
\`summary_files\` integer,
225225
\`summary_diffs\` text,
226226
\`metadata\` text,
227+
\`result\` text,
227228
\`cost\` real DEFAULT 0 NOT NULL,
228229
\`tokens_input\` integer DEFAULT 0 NOT NULL,
229230
\`tokens_output\` integer DEFAULT 0 NOT NULL,
@@ -238,6 +239,7 @@ export default {
238239
\`time_updated\` integer NOT NULL,
239240
\`time_compacting\` integer,
240241
\`time_archived\` integer,
242+
\`context_mode\` text,
241243
CONSTRAINT \`fk_session_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE
242244
);
243245
`)

packages/core/src/session/info.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.In
4141
}),
4242
subpath: row.path ? RelativePath.make(row.path) : undefined,
4343
revert: row.revert ? { ...row.revert, messageID: SessionMessage.ID.make(row.revert.messageID) } : undefined,
44+
result: row.result ?? undefined,
45+
contextMode: row.context_mode ?? undefined,
4446
time: {
4547
created: DateTime.makeUnsafe(row.time_created),
4648
updated: DateTime.makeUnsafe(row.time_updated),

packages/core/src/session/projector.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function sessionRow(info: SessionV1.SessionInfo): typeof SessionTable.$inferInse
6060
summary_files: info.summary?.files,
6161
summary_diffs: info.summary?.diffs ? [...info.summary.diffs] : undefined,
6262
metadata: info.metadata,
63+
result: info.result,
6364
cost: info.cost ?? 0,
6465
tokens_input: (info.tokens ?? { input: 0 }).input,
6566
tokens_output: (info.tokens ?? { output: 0 }).output,
@@ -72,6 +73,7 @@ function sessionRow(info: SessionV1.SessionInfo): typeof SessionTable.$inferInse
7273
time_updated: info.time.updated,
7374
time_compacting: info.time.compacting,
7475
time_archived: info.time.archived,
76+
context_mode: info.contextMode,
7577
}
7678
}
7779

packages/core/src/session/sql.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const SessionTable = sqliteTable(
4040
summary_files: integer(),
4141
summary_diffs: text({ mode: "json" }).$type<Snapshot.LegacyFileDiff[]>(),
4242
metadata: text({ mode: "json" }).$type<Record<string, unknown>>(),
43+
result: text({ mode: "json" }).$type<Record<string, unknown>>(),
4344
cost: real().notNull().default(0),
4445
tokens_input: integer().notNull().default(0),
4546
tokens_output: integer().notNull().default(0),
@@ -57,6 +58,7 @@ export const SessionTable = sqliteTable(
5758
...Timestamps,
5859
time_compacting: integer(),
5960
time_archived: integer(),
61+
context_mode: text().$type<"full" | "sparse">(),
6062
},
6163
(table) => [
6264
index("session_project_idx").on(table.project_id),

packages/core/src/v1/config/agent.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ const AgentSchema = Schema.StructWithRest(
3535
description: "Maximum number of agentic iterations before forcing text-only response",
3636
}),
3737
maxSteps: Schema.optional(PositiveInt).annotate({ description: "@deprecated Use 'steps' field instead." }),
38+
completion: Schema.optional(Schema.Literals(["full", "terse"])).annotate({
39+
description: "Completion display mode for task dispatches from this agent (default: full)",
40+
}),
41+
context: Schema.optional(Schema.Literals(["full", "sparse"])).annotate({
42+
description: "Context display mode for task dispatches from this agent (default: full)",
43+
}),
3844
permission: Schema.optional(ConfigPermissionV1.Info),
3945
}),
4046
[Schema.Record(Schema.String, Schema.Any)],
@@ -57,6 +63,8 @@ const KNOWN_KEYS = new Set([
5763
"permission",
5864
"disable",
5965
"tools",
66+
"completion",
67+
"context",
6068
])
6169

6270
const normalize = (agent: Schema.Schema.Type<typeof AgentSchema>): Schema.Schema.Type<typeof AgentSchema> => {

packages/core/src/v1/config/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ export const Info = Schema.Struct({
166166
}),
167167
}),
168168
),
169+
task: Schema.optional(
170+
Schema.Struct({
171+
completion: Schema.optional(Schema.Literals(["full", "terse"])).annotate({
172+
description: "Completion display mode for task dispatches (default: full)",
173+
}),
174+
context: Schema.optional(Schema.Literals(["full", "sparse"])).annotate({
175+
description: "Context display mode for task dispatches (default: full)",
176+
}),
177+
}),
178+
).annotate({ description: "Task dispatch configuration" }),
169179
experimental: Schema.optional(
170180
Schema.Struct({
171181
disable_paste_summary: Schema.optional(Schema.Boolean),

0 commit comments

Comments
 (0)