@stackbilt/aegis-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbilt/aegis-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Persistent AI agent framework for Cloudflare Workers. Multi-tier memory, autonomous goals, dreaming cycles, MCP native.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -55,7 +55,11 @@
|
|
|
55
55
|
"./routes/pages": "./src/routes/pages.ts",
|
|
56
56
|
"./routes/dynamic-tools": "./src/routes/dynamic-tools.ts",
|
|
57
57
|
"./adapters/voice": "./src/adapters/voice/cloudflare-agent.ts",
|
|
58
|
-
"./schema-enums": "./src/schema-enums.ts"
|
|
58
|
+
"./schema-enums": "./src/schema-enums.ts",
|
|
59
|
+
"./contracts/goal": "./src/contracts/goal.contract.ts",
|
|
60
|
+
"./contracts/agenda-item": "./src/contracts/agenda-item.contract.ts",
|
|
61
|
+
"./contracts/cc-task": "./src/contracts/cc-task.contract.ts",
|
|
62
|
+
"./contracts/memory-entry": "./src/contracts/memory-entry.contract.ts"
|
|
59
63
|
},
|
|
60
64
|
"scripts": {
|
|
61
65
|
"dev": "wrangler dev",
|
|
@@ -70,9 +74,11 @@
|
|
|
70
74
|
"dependencies": {
|
|
71
75
|
"@cloudflare/voice": "^0.1.3",
|
|
72
76
|
"@cloudflare/workers-oauth-provider": "^0.2.4",
|
|
77
|
+
"@stackbilt/contracts": "^0.2.1",
|
|
73
78
|
"@stackbilt/llm-providers": "^1.6.0",
|
|
74
79
|
"agents": "^0.12.3",
|
|
75
|
-
"hono": "^4.12.12"
|
|
80
|
+
"hono": "^4.12.12",
|
|
81
|
+
"zod": "^4.4.3"
|
|
76
82
|
},
|
|
77
83
|
"devDependencies": {
|
|
78
84
|
"@cloudflare/workers-types": "^4.20241218.0",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineContract } from '@stackbilt/contracts';
|
|
3
|
+
|
|
4
|
+
const AgendaPriority = z.enum(['low', 'medium', 'high']);
|
|
5
|
+
const AgendaStatus = z.enum(['active', 'done', 'dismissed']);
|
|
6
|
+
|
|
7
|
+
export const AgendaItemContract = defineContract({
|
|
8
|
+
name: 'AgendaItem',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
description: 'Operator agenda item — work scratchpad with priority and lifecycle tracking',
|
|
11
|
+
|
|
12
|
+
schema: z.object({
|
|
13
|
+
id: z.number().int().positive(),
|
|
14
|
+
item: z.string().min(1),
|
|
15
|
+
context: z.string().nullable(),
|
|
16
|
+
priority: AgendaPriority.default('medium'),
|
|
17
|
+
status: AgendaStatus.default('active'),
|
|
18
|
+
createdAt: z.string().datetime(),
|
|
19
|
+
resolvedAt: z.string().datetime().nullable(),
|
|
20
|
+
businessUnit: z.string().min(1).default('stackbilt'),
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
operations: {
|
|
24
|
+
add: {
|
|
25
|
+
input: z.object({
|
|
26
|
+
item: z.string().min(1),
|
|
27
|
+
context: z.string().optional(),
|
|
28
|
+
priority: AgendaPriority.optional(),
|
|
29
|
+
businessUnit: z.string().min(1).optional(),
|
|
30
|
+
}),
|
|
31
|
+
output: 'self' as const,
|
|
32
|
+
emits: ['agenda_item.added'],
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
resolve: {
|
|
36
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
37
|
+
output: 'self' as const,
|
|
38
|
+
transition: { from: 'active', to: 'done' },
|
|
39
|
+
emits: ['agenda_item.resolved'],
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
dismiss: {
|
|
43
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
44
|
+
output: 'self' as const,
|
|
45
|
+
transition: { from: 'active', to: 'dismissed' },
|
|
46
|
+
emits: ['agenda_item.dismissed'],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
escalate: {
|
|
50
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
51
|
+
output: 'self' as const,
|
|
52
|
+
emits: ['agenda_item.escalated'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
states: {
|
|
57
|
+
field: 'status',
|
|
58
|
+
initial: 'active',
|
|
59
|
+
transitions: {
|
|
60
|
+
active: {
|
|
61
|
+
resolve: 'done',
|
|
62
|
+
dismiss: 'dismissed',
|
|
63
|
+
},
|
|
64
|
+
done: {},
|
|
65
|
+
dismissed: {},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
surfaces: {
|
|
70
|
+
api: {
|
|
71
|
+
basePath: '/api/agenda',
|
|
72
|
+
routes: {
|
|
73
|
+
add: { method: 'POST', path: '/' },
|
|
74
|
+
resolve: { method: 'POST', path: '/:id/resolve' },
|
|
75
|
+
dismiss: { method: 'POST', path: '/:id/dismiss' },
|
|
76
|
+
escalate: { method: 'POST', path: '/:id/escalate' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
db: {
|
|
80
|
+
table: 'agent_agenda',
|
|
81
|
+
indexes: [
|
|
82
|
+
'idx_agenda_status(status)',
|
|
83
|
+
'idx_agenda_priority(priority)',
|
|
84
|
+
'idx_agenda_bu(business_unit, status)',
|
|
85
|
+
],
|
|
86
|
+
columnOverrides: {
|
|
87
|
+
createdAt: { default: "datetime('now')" },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
authority: {
|
|
93
|
+
add: { requires: 'role', roles: ['operator', 'system'] },
|
|
94
|
+
resolve: { requires: 'role', roles: ['operator', 'system'] },
|
|
95
|
+
dismiss: { requires: 'role', roles: ['operator', 'system'] },
|
|
96
|
+
escalate: { requires: 'role', roles: ['system'] },
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
invariants: [
|
|
100
|
+
{
|
|
101
|
+
name: 'resolved_has_timestamp',
|
|
102
|
+
description: 'Done items must have a resolvedAt timestamp',
|
|
103
|
+
check: (entity: unknown) => {
|
|
104
|
+
const a = entity as { status?: string; resolvedAt?: string | null };
|
|
105
|
+
if (a.status === 'done' && !a.resolvedAt) {
|
|
106
|
+
return 'Done agenda item requires resolvedAt';
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
},
|
|
110
|
+
appliesTo: ['resolve'],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineContract } from '@stackbilt/contracts';
|
|
3
|
+
|
|
4
|
+
const TaskStatus = z.enum(['pending', 'running', 'completed', 'failed', 'cancelled']);
|
|
5
|
+
const TaskAuthority = z.enum(['proposed', 'auto_safe', 'operator']);
|
|
6
|
+
const TaskCategory = z.enum(['docs', 'tests', 'research', 'bugfix', 'feature', 'refactor', 'deploy']);
|
|
7
|
+
|
|
8
|
+
export const CCTaskContract = defineContract({
|
|
9
|
+
name: 'CCTask',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
description: 'Claude Code autonomous task — queued, governed, and executed by the task runner',
|
|
12
|
+
|
|
13
|
+
schema: z.object({
|
|
14
|
+
id: z.string().min(1),
|
|
15
|
+
title: z.string().min(1),
|
|
16
|
+
repo: z.string().min(1),
|
|
17
|
+
prompt: z.string().min(1),
|
|
18
|
+
completionSignal: z.string().nullable(),
|
|
19
|
+
status: TaskStatus.default('pending'),
|
|
20
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
21
|
+
dependsOn: z.string().nullable(),
|
|
22
|
+
blockedBy: z.string().nullable(),
|
|
23
|
+
maxTurns: z.number().int().positive().default(25),
|
|
24
|
+
allowedTools: z.string().nullable(),
|
|
25
|
+
sessionId: z.string().nullable(),
|
|
26
|
+
result: z.string().nullable(),
|
|
27
|
+
error: z.string().nullable(),
|
|
28
|
+
exitCode: z.number().int().nullable(),
|
|
29
|
+
preflightJson: z.string().nullable(),
|
|
30
|
+
failureKind: z.string().nullable(),
|
|
31
|
+
retryable: z.boolean().default(false),
|
|
32
|
+
autopsyJson: z.string().nullable(),
|
|
33
|
+
createdAt: z.string().datetime(),
|
|
34
|
+
startedAt: z.string().datetime().nullable(),
|
|
35
|
+
completedAt: z.string().datetime().nullable(),
|
|
36
|
+
createdBy: z.string().min(1).default('operator'),
|
|
37
|
+
authority: TaskAuthority.default('operator'),
|
|
38
|
+
category: TaskCategory.default('feature'),
|
|
39
|
+
branch: z.string().nullable(),
|
|
40
|
+
prUrl: z.string().nullable(),
|
|
41
|
+
utilityJson: z.string().nullable(),
|
|
42
|
+
githubIssueRepo: z.string().nullable(),
|
|
43
|
+
githubIssueNumber: z.number().int().positive().nullable(),
|
|
44
|
+
businessUnit: z.string().min(1).default('stackbilt'),
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
operations: {
|
|
48
|
+
create: {
|
|
49
|
+
input: z.object({
|
|
50
|
+
id: z.string().min(1),
|
|
51
|
+
title: z.string().min(1),
|
|
52
|
+
repo: z.string().min(1),
|
|
53
|
+
prompt: z.string().min(1),
|
|
54
|
+
completionSignal: z.string().optional(),
|
|
55
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
56
|
+
dependsOn: z.string().optional(),
|
|
57
|
+
blockedBy: z.string().optional(),
|
|
58
|
+
maxTurns: z.number().int().positive().optional(),
|
|
59
|
+
allowedTools: z.string().optional(),
|
|
60
|
+
authority: TaskAuthority.optional(),
|
|
61
|
+
category: TaskCategory.optional(),
|
|
62
|
+
githubIssueRepo: z.string().optional(),
|
|
63
|
+
githubIssueNumber: z.number().int().positive().optional(),
|
|
64
|
+
businessUnit: z.string().min(1).optional(),
|
|
65
|
+
}),
|
|
66
|
+
output: 'self' as const,
|
|
67
|
+
emits: ['cc_task.created'],
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
start: {
|
|
71
|
+
input: z.object({
|
|
72
|
+
id: z.string().min(1),
|
|
73
|
+
sessionId: z.string().min(1),
|
|
74
|
+
}),
|
|
75
|
+
output: 'self' as const,
|
|
76
|
+
transition: { from: 'pending', to: 'running' },
|
|
77
|
+
emits: ['cc_task.started'],
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
complete: {
|
|
81
|
+
input: z.object({
|
|
82
|
+
id: z.string().min(1),
|
|
83
|
+
result: z.string().optional(),
|
|
84
|
+
exitCode: z.number().int().optional(),
|
|
85
|
+
prUrl: z.string().optional(),
|
|
86
|
+
utilityJson: z.string().optional(),
|
|
87
|
+
}),
|
|
88
|
+
output: 'self' as const,
|
|
89
|
+
transition: { from: 'running', to: 'completed' },
|
|
90
|
+
emits: ['cc_task.completed'],
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
fail: {
|
|
94
|
+
input: z.object({
|
|
95
|
+
id: z.string().min(1),
|
|
96
|
+
error: z.string().min(1),
|
|
97
|
+
exitCode: z.number().int().optional(),
|
|
98
|
+
failureKind: z.string().optional(),
|
|
99
|
+
retryable: z.boolean().optional(),
|
|
100
|
+
autopsyJson: z.string().optional(),
|
|
101
|
+
}),
|
|
102
|
+
output: 'self' as const,
|
|
103
|
+
transition: { from: 'running', to: 'failed' },
|
|
104
|
+
emits: ['cc_task.failed'],
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
cancel: {
|
|
108
|
+
input: z.object({ id: z.string().min(1) }),
|
|
109
|
+
output: 'self' as const,
|
|
110
|
+
transition: { from: ['pending', 'running'], to: 'cancelled' },
|
|
111
|
+
emits: ['cc_task.cancelled'],
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
approve: {
|
|
115
|
+
input: z.object({ id: z.string().min(1) }),
|
|
116
|
+
output: 'self' as const,
|
|
117
|
+
emits: ['cc_task.approved'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
states: {
|
|
122
|
+
field: 'status',
|
|
123
|
+
initial: 'pending',
|
|
124
|
+
transitions: {
|
|
125
|
+
pending: {
|
|
126
|
+
start: 'running',
|
|
127
|
+
cancel: 'cancelled',
|
|
128
|
+
},
|
|
129
|
+
running: {
|
|
130
|
+
complete: 'completed',
|
|
131
|
+
fail: 'failed',
|
|
132
|
+
cancel: 'cancelled',
|
|
133
|
+
},
|
|
134
|
+
completed: {},
|
|
135
|
+
failed: {},
|
|
136
|
+
cancelled: {},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
surfaces: {
|
|
141
|
+
api: {
|
|
142
|
+
basePath: '/api/cc-tasks',
|
|
143
|
+
routes: {
|
|
144
|
+
create: { method: 'POST', path: '/' },
|
|
145
|
+
start: { method: 'POST', path: '/:id/start' },
|
|
146
|
+
complete: { method: 'POST', path: '/:id/complete' },
|
|
147
|
+
fail: { method: 'POST', path: '/:id/fail' },
|
|
148
|
+
cancel: { method: 'POST', path: '/:id/cancel' },
|
|
149
|
+
approve: { method: 'POST', path: '/:id/approve' },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
db: {
|
|
153
|
+
table: 'cc_tasks',
|
|
154
|
+
indexes: [
|
|
155
|
+
'idx_cc_tasks_status(status, priority)',
|
|
156
|
+
'idx_cc_tasks_depends(depends_on)',
|
|
157
|
+
'idx_cc_tasks_created(created_at)',
|
|
158
|
+
'idx_cc_tasks_bu(business_unit, status)',
|
|
159
|
+
'idx_cc_tasks_authority(authority)',
|
|
160
|
+
'idx_cc_tasks_gh_issue(github_issue_repo, github_issue_number)',
|
|
161
|
+
'idx_cc_tasks_failure_kind(failure_kind, completed_at)',
|
|
162
|
+
],
|
|
163
|
+
columnOverrides: {
|
|
164
|
+
createdAt: { default: "datetime('now')" },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
authority: {
|
|
170
|
+
create: { requires: 'role', roles: ['operator', 'system'] },
|
|
171
|
+
start: { requires: 'role', roles: ['system'] },
|
|
172
|
+
complete: { requires: 'role', roles: ['system'] },
|
|
173
|
+
fail: { requires: 'role', roles: ['system'] },
|
|
174
|
+
cancel: { requires: 'role', roles: ['operator', 'system'] },
|
|
175
|
+
approve: { requires: 'role', roles: ['operator'] },
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
invariants: [
|
|
179
|
+
{
|
|
180
|
+
name: 'proposed_task_needs_approval',
|
|
181
|
+
description: 'Proposed tasks must be approved before they can run',
|
|
182
|
+
check: (entity: unknown) => {
|
|
183
|
+
const t = entity as { authority?: string; status?: string };
|
|
184
|
+
if (t.authority === 'proposed' && t.status === 'running') {
|
|
185
|
+
return 'Proposed tasks require approval before execution';
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
appliesTo: ['start'],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'completed_has_timestamp',
|
|
193
|
+
description: 'Completed or failed tasks must have a completedAt timestamp',
|
|
194
|
+
check: (entity: unknown) => {
|
|
195
|
+
const t = entity as { status?: string; completedAt?: string | null };
|
|
196
|
+
if ((t.status === 'completed' || t.status === 'failed') && !t.completedAt) {
|
|
197
|
+
return 'Terminal tasks require completedAt';
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
},
|
|
201
|
+
appliesTo: ['complete', 'fail'],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineContract } from '@stackbilt/contracts';
|
|
3
|
+
|
|
4
|
+
const GoalStatus = z.enum(['active', 'paused', 'completed', 'failed']);
|
|
5
|
+
const AuthorityLevel = z.enum(['propose', 'auto_low', 'auto_high']);
|
|
6
|
+
|
|
7
|
+
export const GoalContract = defineContract({
|
|
8
|
+
name: 'Goal',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
description: 'Autonomous agent goal with scheduled execution and authority-gated autonomy',
|
|
11
|
+
|
|
12
|
+
schema: z.object({
|
|
13
|
+
id: z.string().min(1),
|
|
14
|
+
title: z.string().min(1),
|
|
15
|
+
description: z.string().nullable(),
|
|
16
|
+
status: GoalStatus.default('active'),
|
|
17
|
+
authorityLevel: AuthorityLevel.default('propose'),
|
|
18
|
+
scheduleHours: z.number().int().positive().default(6),
|
|
19
|
+
createdAt: z.string().datetime(),
|
|
20
|
+
lastRunAt: z.string().datetime().nullable(),
|
|
21
|
+
nextRunAt: z.string().datetime().nullable(),
|
|
22
|
+
completedAt: z.string().datetime().nullable(),
|
|
23
|
+
runCount: z.number().int().nonnegative().default(0),
|
|
24
|
+
contextJson: z.string().nullable(),
|
|
25
|
+
businessUnit: z.string().min(1).default('stackbilt'),
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
operations: {
|
|
29
|
+
create: {
|
|
30
|
+
input: z.object({
|
|
31
|
+
id: z.string().min(1),
|
|
32
|
+
title: z.string().min(1),
|
|
33
|
+
description: z.string().optional(),
|
|
34
|
+
authorityLevel: AuthorityLevel.optional(),
|
|
35
|
+
scheduleHours: z.number().int().positive().optional(),
|
|
36
|
+
contextJson: z.string().optional(),
|
|
37
|
+
businessUnit: z.string().min(1).optional(),
|
|
38
|
+
}),
|
|
39
|
+
output: 'self' as const,
|
|
40
|
+
emits: ['goal.created'],
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
pause: {
|
|
44
|
+
input: z.object({ id: z.string().min(1) }),
|
|
45
|
+
output: 'self' as const,
|
|
46
|
+
transition: { from: 'active', to: 'paused' },
|
|
47
|
+
emits: ['goal.paused'],
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
resume: {
|
|
51
|
+
input: z.object({ id: z.string().min(1) }),
|
|
52
|
+
output: 'self' as const,
|
|
53
|
+
transition: { from: 'paused', to: 'active' },
|
|
54
|
+
emits: ['goal.resumed'],
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
complete: {
|
|
58
|
+
input: z.object({ id: z.string().min(1) }),
|
|
59
|
+
output: 'self' as const,
|
|
60
|
+
transition: { from: ['active', 'paused'], to: 'completed' },
|
|
61
|
+
emits: ['goal.completed'],
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
fail: {
|
|
65
|
+
input: z.object({ id: z.string().min(1) }),
|
|
66
|
+
output: 'self' as const,
|
|
67
|
+
transition: { from: ['active', 'paused'], to: 'failed' },
|
|
68
|
+
emits: ['goal.failed'],
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
recordRun: {
|
|
72
|
+
input: z.object({
|
|
73
|
+
id: z.string().min(1),
|
|
74
|
+
nextRunAt: z.string().datetime().optional(),
|
|
75
|
+
}),
|
|
76
|
+
output: 'self' as const,
|
|
77
|
+
emits: ['goal.ran'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
states: {
|
|
82
|
+
field: 'status',
|
|
83
|
+
initial: 'active',
|
|
84
|
+
transitions: {
|
|
85
|
+
active: {
|
|
86
|
+
pause: 'paused',
|
|
87
|
+
complete: 'completed',
|
|
88
|
+
fail: 'failed',
|
|
89
|
+
},
|
|
90
|
+
paused: {
|
|
91
|
+
resume: 'active',
|
|
92
|
+
complete: 'completed',
|
|
93
|
+
fail: 'failed',
|
|
94
|
+
},
|
|
95
|
+
completed: {},
|
|
96
|
+
failed: {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
surfaces: {
|
|
101
|
+
api: {
|
|
102
|
+
basePath: '/api/goals',
|
|
103
|
+
routes: {
|
|
104
|
+
create: { method: 'POST', path: '/' },
|
|
105
|
+
pause: { method: 'POST', path: '/:id/pause' },
|
|
106
|
+
resume: { method: 'POST', path: '/:id/resume' },
|
|
107
|
+
complete: { method: 'POST', path: '/:id/complete' },
|
|
108
|
+
fail: { method: 'POST', path: '/:id/fail' },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
db: {
|
|
112
|
+
table: 'agent_goals',
|
|
113
|
+
indexes: [
|
|
114
|
+
'idx_goals_status(status)',
|
|
115
|
+
'idx_goals_next_run(next_run_at)',
|
|
116
|
+
'idx_goals_bu(business_unit, status)',
|
|
117
|
+
],
|
|
118
|
+
columnOverrides: {
|
|
119
|
+
createdAt: { default: "datetime('now')" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
authority: {
|
|
125
|
+
create: { requires: 'role', roles: ['operator', 'system'] },
|
|
126
|
+
pause: { requires: 'role', roles: ['operator', 'system'] },
|
|
127
|
+
resume: { requires: 'role', roles: ['operator', 'system'] },
|
|
128
|
+
complete: { requires: 'role', roles: ['system'] },
|
|
129
|
+
fail: { requires: 'role', roles: ['system'] },
|
|
130
|
+
recordRun: { requires: 'role', roles: ['system'] },
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
invariants: [
|
|
134
|
+
{
|
|
135
|
+
name: 'completed_has_timestamp',
|
|
136
|
+
description: 'Completed goals must have a completedAt timestamp',
|
|
137
|
+
check: (entity: unknown) => {
|
|
138
|
+
const g = entity as { status?: string; completedAt?: string | null };
|
|
139
|
+
if (g.status === 'completed' && !g.completedAt) {
|
|
140
|
+
return 'Completed goal requires completedAt';
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
},
|
|
144
|
+
appliesTo: ['complete'],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineContract } from '@stackbilt/contracts';
|
|
3
|
+
|
|
4
|
+
const ValidationStage = z.enum(['candidate', 'validated', 'expert', 'canonical', 'refuted']);
|
|
5
|
+
|
|
6
|
+
export const MemoryEntryContract = defineContract({
|
|
7
|
+
name: 'MemoryEntry',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
description: 'Semantic memory entry with topic-scoped facts, CRIX validation pipeline, and decay tracking',
|
|
10
|
+
|
|
11
|
+
schema: z.object({
|
|
12
|
+
id: z.number().int().positive(),
|
|
13
|
+
topic: z.string().min(1),
|
|
14
|
+
fact: z.string().min(1),
|
|
15
|
+
factHash: z.string().default(''),
|
|
16
|
+
confidence: z.number().min(0).max(1).default(0.8),
|
|
17
|
+
source: z.string().min(1),
|
|
18
|
+
createdAt: z.string().datetime(),
|
|
19
|
+
updatedAt: z.string().datetime(),
|
|
20
|
+
expiresAt: z.string().datetime().nullable(),
|
|
21
|
+
validUntil: z.string().datetime().nullable(),
|
|
22
|
+
supersededBy: z.number().int().positive().nullable(),
|
|
23
|
+
strength: z.number().int().nonnegative().default(1),
|
|
24
|
+
lastRecalledAt: z.string().datetime().nullable(),
|
|
25
|
+
validationStage: ValidationStage.default('candidate'),
|
|
26
|
+
validators: z.string().nullable(),
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
operations: {
|
|
30
|
+
record: {
|
|
31
|
+
input: z.object({
|
|
32
|
+
topic: z.string().min(1),
|
|
33
|
+
fact: z.string().min(1),
|
|
34
|
+
source: z.string().min(1),
|
|
35
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
36
|
+
expiresAt: z.string().datetime().optional(),
|
|
37
|
+
}),
|
|
38
|
+
output: 'self' as const,
|
|
39
|
+
emits: ['memory_entry.recorded'],
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
validate: {
|
|
43
|
+
input: z.object({
|
|
44
|
+
id: z.number().int().positive(),
|
|
45
|
+
repo: z.string().min(1),
|
|
46
|
+
confirmed: z.boolean(),
|
|
47
|
+
date: z.string().datetime(),
|
|
48
|
+
}),
|
|
49
|
+
output: 'self' as const,
|
|
50
|
+
transition: { from: 'candidate', to: 'validated' },
|
|
51
|
+
emits: ['memory_entry.validated'],
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
promoteToExpert: {
|
|
55
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
56
|
+
output: 'self' as const,
|
|
57
|
+
transition: { from: 'validated', to: 'expert' },
|
|
58
|
+
emits: ['memory_entry.promoted_expert'],
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
promoteToCanonical: {
|
|
62
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
63
|
+
output: 'self' as const,
|
|
64
|
+
transition: { from: 'expert', to: 'canonical' },
|
|
65
|
+
emits: ['memory_entry.promoted_canonical'],
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
refute: {
|
|
69
|
+
input: z.object({
|
|
70
|
+
id: z.number().int().positive(),
|
|
71
|
+
supersededBy: z.number().int().positive().optional(),
|
|
72
|
+
}),
|
|
73
|
+
output: 'self' as const,
|
|
74
|
+
transition: { from: ['candidate', 'validated', 'expert'], to: 'refuted' },
|
|
75
|
+
emits: ['memory_entry.refuted'],
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
recall: {
|
|
79
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
80
|
+
output: 'self' as const,
|
|
81
|
+
emits: ['memory_entry.recalled'],
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
expire: {
|
|
85
|
+
input: z.object({ id: z.number().int().positive() }),
|
|
86
|
+
output: 'self' as const,
|
|
87
|
+
emits: ['memory_entry.expired'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
states: {
|
|
92
|
+
field: 'validationStage',
|
|
93
|
+
initial: 'candidate',
|
|
94
|
+
transitions: {
|
|
95
|
+
candidate: {
|
|
96
|
+
validate: 'validated',
|
|
97
|
+
refute: 'refuted',
|
|
98
|
+
},
|
|
99
|
+
validated: {
|
|
100
|
+
promoteToExpert: 'expert',
|
|
101
|
+
refute: 'refuted',
|
|
102
|
+
},
|
|
103
|
+
expert: {
|
|
104
|
+
promoteToCanonical: 'canonical',
|
|
105
|
+
refute: 'refuted',
|
|
106
|
+
},
|
|
107
|
+
canonical: {},
|
|
108
|
+
refuted: {},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
surfaces: {
|
|
113
|
+
api: {
|
|
114
|
+
basePath: '/api/memory',
|
|
115
|
+
routes: {
|
|
116
|
+
record: { method: 'POST', path: '/' },
|
|
117
|
+
validate: { method: 'POST', path: '/:id/validate' },
|
|
118
|
+
promoteToExpert: { method: 'POST', path: '/:id/promote-expert' },
|
|
119
|
+
promoteToCanonical: { method: 'POST', path: '/:id/promote-canonical' },
|
|
120
|
+
refute: { method: 'POST', path: '/:id/refute' },
|
|
121
|
+
recall: { method: 'POST', path: '/:id/recall' },
|
|
122
|
+
expire: { method: 'POST', path: '/:id/expire' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
db: {
|
|
126
|
+
table: 'memory_entries',
|
|
127
|
+
indexes: [
|
|
128
|
+
'idx_memory_topic(topic)',
|
|
129
|
+
'idx_memory_dedup(topic, fact_hash)',
|
|
130
|
+
'idx_memory_expires(expires_at)',
|
|
131
|
+
'idx_memory_valid(valid_until)',
|
|
132
|
+
'idx_memory_validation_stage(validation_stage)',
|
|
133
|
+
],
|
|
134
|
+
columnOverrides: {
|
|
135
|
+
createdAt: { default: "datetime('now')" },
|
|
136
|
+
updatedAt: { default: "datetime('now')" },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
authority: {
|
|
142
|
+
record: { requires: 'role', roles: ['operator', 'system'] },
|
|
143
|
+
validate: { requires: 'role', roles: ['system'] },
|
|
144
|
+
promoteToExpert: { requires: 'role', roles: ['system'] },
|
|
145
|
+
promoteToCanonical: { requires: 'role', roles: ['system'] },
|
|
146
|
+
refute: { requires: 'role', roles: ['operator', 'system'] },
|
|
147
|
+
recall: { requires: 'role', roles: ['system'] },
|
|
148
|
+
expire: { requires: 'role', roles: ['system'] },
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
invariants: [
|
|
152
|
+
{
|
|
153
|
+
name: 'refuted_entry_not_canonical',
|
|
154
|
+
description: 'Canonical entries cannot be refuted — they must be superseded instead',
|
|
155
|
+
check: (entity: unknown) => {
|
|
156
|
+
const m = entity as { validationStage?: string };
|
|
157
|
+
if (m.validationStage === 'canonical') {
|
|
158
|
+
return 'Canonical entries cannot transition to refuted';
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
},
|
|
162
|
+
appliesTo: ['refute'],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'high_confidence_for_canonical',
|
|
166
|
+
description: 'Canonical entries should have confidence >= 0.9',
|
|
167
|
+
check: (entity: unknown) => {
|
|
168
|
+
const m = entity as { validationStage?: string; confidence?: number };
|
|
169
|
+
if (m.validationStage === 'canonical' && (m.confidence ?? 0) < 0.9) {
|
|
170
|
+
return 'Canonical memory entries require confidence >= 0.9';
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
},
|
|
174
|
+
appliesTo: ['promoteToCanonical'],
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|