@namzu/sdk 61.4.0 → 62.1.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/CHANGELOG.md +62 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/acp/permission.d.ts +8 -0
- package/dist/bridge/acp/permission.d.ts.map +1 -1
- package/dist/bridge/acp/permission.js.map +1 -1
- package/dist/bridge/acp/server.d.ts.map +1 -1
- package/dist/bridge/acp/server.js +8 -1
- package/dist/bridge/acp/server.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +13 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/constants/tools/index.d.ts +2 -0
- package/dist/constants/tools/index.d.ts.map +1 -1
- package/dist/constants/tools/index.js +23 -0
- package/dist/constants/tools/index.js.map +1 -1
- package/dist/contracts/session/api.d.ts +1 -1
- package/dist/contracts/session/api.d.ts.map +1 -1
- package/dist/eval/harness-protection.js +1 -1
- package/dist/eval/harness-protection.js.map +1 -1
- package/dist/learning/cycle.d.ts +5 -3
- package/dist/learning/cycle.d.ts.map +1 -1
- package/dist/learning/cycle.js +11 -13
- package/dist/learning/cycle.js.map +1 -1
- package/dist/learning/guidance.d.ts +74 -0
- package/dist/learning/guidance.d.ts.map +1 -0
- package/dist/learning/guidance.js +259 -0
- package/dist/learning/guidance.js.map +1 -0
- package/dist/learning/skill.d.ts +2 -2
- package/dist/learning/skill.d.ts.map +1 -1
- package/dist/learning/skill.js +6 -6
- package/dist/learning/skill.js.map +1 -1
- package/dist/learning/store.d.ts.map +1 -1
- package/dist/learning/store.js +6 -6
- package/dist/learning/store.js.map +1 -1
- package/dist/pals/communication/ingress-schema.d.ts +8 -8
- package/dist/pals/communication/schema.d.ts +2 -2
- package/dist/peers/client.d.ts +2 -3
- package/dist/peers/client.d.ts.map +1 -1
- package/dist/peers/client.js +2 -3
- package/dist/peers/client.js.map +1 -1
- package/dist/peers/endpoint.d.ts +1 -2
- package/dist/peers/endpoint.d.ts.map +1 -1
- package/dist/peers/endpoint.js +1 -2
- package/dist/peers/endpoint.js.map +1 -1
- package/dist/peers/protocol.d.ts.map +1 -1
- package/dist/peers/protocol.js +1 -0
- package/dist/peers/protocol.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +4 -4
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/prompt/index.d.ts +0 -4
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/index.js +0 -2
- package/dist/prompt/index.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +3 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/model-window-suffix.d.ts.map +1 -1
- package/dist/provider/model-window-suffix.js +1 -0
- package/dist/provider/model-window-suffix.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/provider/step-stopped.d.ts +19 -0
- package/dist/provider/step-stopped.d.ts.map +1 -0
- package/dist/provider/step-stopped.js +25 -0
- package/dist/provider/step-stopped.js.map +1 -0
- package/dist/provider/token-budget.d.ts.map +1 -1
- package/dist/provider/token-budget.js +6 -1
- package/dist/provider/token-budget.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -15
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -15
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +0 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +0 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +4 -22
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +17 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +176 -6
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/per-call-review.js +4 -5
- package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +6 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/recorded-context.d.ts +1 -1
- package/dist/runtime/query/iteration/recorded-context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/recorded-context.js +2 -1
- package/dist/runtime/query/iteration/recorded-context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts +9 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +48 -2
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +7 -4
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +40 -8
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +139 -24
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +32 -0
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -0
- package/dist/runtime/query/turn/completion-audit.js +56 -0
- package/dist/runtime/query/turn/completion-audit.js.map +1 -0
- package/dist/runtime/query/turn/stall-monitor.d.ts +78 -0
- package/dist/runtime/query/turn/stall-monitor.d.ts.map +1 -0
- package/dist/runtime/query/turn/stall-monitor.js +101 -0
- package/dist/runtime/query/turn/stall-monitor.js.map +1 -0
- package/dist/session/paths.d.ts +1 -2
- package/dist/session/paths.d.ts.map +1 -1
- package/dist/session/paths.js +1 -4
- package/dist/session/paths.js.map +1 -1
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +2 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +5 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/content-fingerprint.d.ts +13 -0
- package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
- package/dist/tools/builtins/content-fingerprint.js +25 -0
- package/dist/tools/builtins/content-fingerprint.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +7 -2
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/shell-read-credit.d.ts +15 -0
- package/dist/tools/builtins/shell-read-credit.d.ts.map +1 -0
- package/dist/tools/builtins/shell-read-credit.js +161 -0
- package/dist/tools/builtins/shell-read-credit.js.map +1 -0
- package/dist/tools/paths.d.ts +15 -2
- package/dist/tools/paths.d.ts.map +1 -1
- package/dist/tools/paths.js +17 -2
- package/dist/tools/paths.js.map +1 -1
- package/dist/turn/evidence-recall.d.ts +1 -1
- package/dist/turn/evidence-recall.d.ts.map +1 -1
- package/dist/turn/evidence-recall.js +5 -14
- package/dist/turn/evidence-recall.js.map +1 -1
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +18 -4
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/turn/reporter.d.ts.map +1 -1
- package/dist/turn/reporter.js +5 -0
- package/dist/turn/reporter.js.map +1 -1
- package/dist/types/acp/index.d.ts +6 -0
- package/dist/types/acp/index.d.ts.map +1 -1
- package/dist/types/agent/base.d.ts +4 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +8 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/ids/index.d.ts +1 -1
- package/dist/types/message/index.d.ts +1 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +1 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/provider/interface.d.ts +12 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/session/config.d.ts +25 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +19 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/prepare-step.d.ts +16 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +3 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +1 -1
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +2 -0
- package/src/bridge/a2a/mapper.ts +2 -0
- package/src/bridge/acp/permission.ts +8 -0
- package/src/bridge/acp/server.ts +7 -1
- package/src/bridge/sse/mapper.ts +13 -0
- package/src/constants/tools/index.ts +27 -0
- package/src/contracts/session/api.ts +1 -0
- package/src/eval/harness-protection.ts +1 -1
- package/src/learning/cycle.ts +15 -16
- package/src/learning/guidance.ts +339 -0
- package/src/learning/skill.ts +7 -7
- package/src/learning/store.ts +5 -6
- package/src/peers/client.ts +2 -3
- package/src/peers/endpoint.ts +1 -2
- package/src/peers/protocol.ts +1 -0
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/prompt/index.ts +0 -4
- package/src/provider/fallback.ts +7 -0
- package/src/provider/idle-timeout.ts +8 -0
- package/src/provider/model-window-suffix.ts +1 -0
- package/src/provider/retry.ts +8 -0
- package/src/provider/step-stopped.ts +27 -0
- package/src/provider/token-budget.ts +6 -1
- package/src/public-runtime.ts +7 -28
- package/src/public-tools.ts +0 -3
- package/src/public-types.ts +6 -137
- package/src/runtime/query/iteration/index.ts +218 -6
- package/src/runtime/query/iteration/phases/per-call-review.ts +7 -5
- package/src/runtime/query/iteration/phases/tool-review.ts +5 -3
- package/src/runtime/query/iteration/recorded-context.ts +2 -0
- package/src/runtime/query/iteration/stream-turn.ts +51 -1
- package/src/runtime/query/resume-pending.ts +11 -4
- package/src/runtime/query/review-policy.ts +157 -24
- package/src/runtime/query/turn/completion-audit.ts +71 -0
- package/src/runtime/query/turn/stall-monitor.ts +158 -0
- package/src/session/paths.ts +2 -6
- package/src/store/index.ts +2 -0
- package/src/tools/builtins/bash.ts +6 -2
- package/src/tools/builtins/content-fingerprint.ts +25 -0
- package/src/tools/builtins/edit.ts +11 -2
- package/src/tools/builtins/shell-read-credit.ts +159 -0
- package/src/tools/paths.ts +18 -2
- package/src/turn/evidence-recall.ts +7 -15
- package/src/turn/memory-recall.ts +26 -7
- package/src/turn/reporter.ts +5 -0
- package/src/types/acp/index.ts +6 -0
- package/src/types/agent/base.ts +4 -0
- package/src/types/hitl/index.ts +8 -0
- package/src/types/ids/index.ts +1 -1
- package/src/types/message/index.ts +1 -0
- package/src/types/provider/interface.ts +13 -0
- package/src/types/session/config.ts +27 -0
- package/src/types/session/events.ts +19 -0
- package/src/types/session/prepare-step.ts +17 -0
- package/src/types/session/records.ts +3 -0
- package/src/types/session/turn.ts +2 -1
- package/dist/learning/deprecated.d.ts +0 -86
- package/dist/learning/deprecated.d.ts.map +0 -1
- package/dist/learning/deprecated.js +0 -22
- package/dist/learning/deprecated.js.map +0 -1
- package/dist/manager/resident/activity.d.ts +0 -74
- package/dist/manager/resident/activity.d.ts.map +0 -1
- package/dist/manager/resident/activity.js +0 -125
- package/dist/manager/resident/activity.js.map +0 -1
- package/dist/manager/resident/agenda.d.ts +0 -167
- package/dist/manager/resident/agenda.d.ts.map +0 -1
- package/dist/manager/resident/agenda.js +0 -588
- package/dist/manager/resident/agenda.js.map +0 -1
- package/dist/manager/resident/consumption.d.ts +0 -98
- package/dist/manager/resident/consumption.d.ts.map +0 -1
- package/dist/manager/resident/consumption.js +0 -237
- package/dist/manager/resident/consumption.js.map +0 -1
- package/dist/manager/resident/delivery-window.d.ts +0 -28
- package/dist/manager/resident/delivery-window.d.ts.map +0 -1
- package/dist/manager/resident/delivery-window.js +0 -80
- package/dist/manager/resident/delivery-window.js.map +0 -1
- package/dist/manager/resident/evidence-recall.d.ts +0 -30
- package/dist/manager/resident/evidence-recall.d.ts.map +0 -1
- package/dist/manager/resident/evidence-recall.js +0 -297
- package/dist/manager/resident/evidence-recall.js.map +0 -1
- package/dist/manager/resident/history-disk.d.ts +0 -10
- package/dist/manager/resident/history-disk.d.ts.map +0 -1
- package/dist/manager/resident/history-disk.js +0 -50
- package/dist/manager/resident/history-disk.js.map +0 -1
- package/dist/manager/resident/history.d.ts +0 -115
- package/dist/manager/resident/history.d.ts.map +0 -1
- package/dist/manager/resident/history.js +0 -203
- package/dist/manager/resident/history.js.map +0 -1
- package/dist/manager/resident/host.d.ts +0 -112
- package/dist/manager/resident/host.d.ts.map +0 -1
- package/dist/manager/resident/host.js +0 -254
- package/dist/manager/resident/host.js.map +0 -1
- package/dist/manager/resident/initiative.d.ts +0 -86
- package/dist/manager/resident/initiative.d.ts.map +0 -1
- package/dist/manager/resident/initiative.js +0 -131
- package/dist/manager/resident/initiative.js.map +0 -1
- package/dist/manager/resident/loop.d.ts +0 -49
- package/dist/manager/resident/loop.d.ts.map +0 -1
- package/dist/manager/resident/loop.js +0 -75
- package/dist/manager/resident/loop.js.map +0 -1
- package/dist/manager/resident/outbox.d.ts +0 -124
- package/dist/manager/resident/outbox.d.ts.map +0 -1
- package/dist/manager/resident/outbox.js +0 -216
- package/dist/manager/resident/outbox.js.map +0 -1
- package/dist/manager/resident/proposal.d.ts +0 -65
- package/dist/manager/resident/proposal.d.ts.map +0 -1
- package/dist/manager/resident/proposal.js +0 -77
- package/dist/manager/resident/proposal.js.map +0 -1
- package/dist/manager/resident/store.d.ts +0 -108
- package/dist/manager/resident/store.d.ts.map +0 -1
- package/dist/manager/resident/store.js +0 -205
- package/dist/manager/resident/store.js.map +0 -1
- package/dist/manager/resident/tool-evidence.d.ts +0 -112
- package/dist/manager/resident/tool-evidence.d.ts.map +0 -1
- package/dist/manager/resident/tool-evidence.js +0 -289
- package/dist/manager/resident/tool-evidence.js.map +0 -1
- package/dist/prompt/resident-learning.d.ts +0 -29
- package/dist/prompt/resident-learning.d.ts.map +0 -1
- package/dist/prompt/resident-learning.js +0 -126
- package/dist/prompt/resident-learning.js.map +0 -1
- package/dist/prompt/resident-step.d.ts +0 -42
- package/dist/prompt/resident-step.d.ts.map +0 -1
- package/dist/prompt/resident-step.js +0 -137
- package/dist/prompt/resident-step.js.map +0 -1
- package/dist/tools/resident-history.d.ts +0 -12
- package/dist/tools/resident-history.d.ts.map +0 -1
- package/dist/tools/resident-history.js +0 -82
- package/dist/tools/resident-history.js.map +0 -1
- package/dist/tools/resident-tool-evidence.d.ts +0 -9
- package/dist/tools/resident-tool-evidence.d.ts.map +0 -1
- package/dist/tools/resident-tool-evidence.js +0 -61
- package/dist/tools/resident-tool-evidence.js.map +0 -1
- package/src/learning/deprecated.ts +0 -124
- package/src/manager/resident/activity.ts +0 -211
- package/src/manager/resident/agenda.ts +0 -937
- package/src/manager/resident/consumption.ts +0 -335
- package/src/manager/resident/delivery-window.ts +0 -99
- package/src/manager/resident/evidence-recall.ts +0 -369
- package/src/manager/resident/history-disk.ts +0 -63
- package/src/manager/resident/history.ts +0 -348
- package/src/manager/resident/host.ts +0 -390
- package/src/manager/resident/initiative.ts +0 -231
- package/src/manager/resident/loop.ts +0 -119
- package/src/manager/resident/outbox.ts +0 -341
- package/src/manager/resident/proposal.ts +0 -119
- package/src/manager/resident/store.ts +0 -279
- package/src/manager/resident/tool-evidence.ts +0 -455
- package/src/prompt/resident-learning.ts +0 -152
- package/src/prompt/resident-step.ts +0 -182
- package/src/tools/resident-history.ts +0 -88
- package/src/tools/resident-tool-evidence.ts +0 -72
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readdir } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import { DiskRecordStore } from '../store/kv/record-store.js'
|
|
7
|
+
import { DiskRevisionRecordStore, revisionFileSegment } from '../store/kv/revision-record-store.js'
|
|
8
|
+
import { defineSchema } from '../store/schema.js'
|
|
9
|
+
import type { TenantId } from '../types/ids/index.js'
|
|
10
|
+
import { asTenantId } from '../utils/id.js'
|
|
11
|
+
import {
|
|
12
|
+
type LearningEvidence,
|
|
13
|
+
type LearningProfileUpdate,
|
|
14
|
+
type LearningSkillCandidate,
|
|
15
|
+
type LearningSkillEvaluation,
|
|
16
|
+
type LearningState,
|
|
17
|
+
freezeLearning,
|
|
18
|
+
learningEvidenceSchema,
|
|
19
|
+
learningSchema,
|
|
20
|
+
promoteLearningSkill,
|
|
21
|
+
restoreLearningSkill,
|
|
22
|
+
reviseLearningProfile,
|
|
23
|
+
} from './skill.js'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @experimental One immutable revision of the guidance a learning target has
|
|
27
|
+
* earned. `learning` is absent until the first profile change or activation.
|
|
28
|
+
*/
|
|
29
|
+
export interface LearningGuidanceState {
|
|
30
|
+
readonly tenantId: string
|
|
31
|
+
readonly agentKey: string
|
|
32
|
+
readonly revision: number
|
|
33
|
+
readonly learning?: LearningState
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @experimental A competing writer advanced the guidance revision; read it again before retrying. */
|
|
37
|
+
export class LearningGuidanceConflictError extends Error {
|
|
38
|
+
constructor() {
|
|
39
|
+
super('Learning guidance changed; read the current guidance before retrying.')
|
|
40
|
+
this.name = 'LearningGuidanceConflictError'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const guidanceSchema = z.object({
|
|
45
|
+
tenantId: z.string().uuid(),
|
|
46
|
+
agentKey: z.string().min(1).max(200),
|
|
47
|
+
revision: z.number().int().positive().safe(),
|
|
48
|
+
learning: learningSchema.optional(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const guidanceRecordSchema = defineSchema({
|
|
52
|
+
kind: 'learning-guidance',
|
|
53
|
+
current: 1,
|
|
54
|
+
migrations: {},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The record format learning guidance was kept in before it had a store of its
|
|
59
|
+
* own. Its `kind` is the identifier stamped into those files, so it keeps its
|
|
60
|
+
* spelling; only the fields learning used are read from it.
|
|
61
|
+
*/
|
|
62
|
+
const earlierRecordSchema = defineSchema({
|
|
63
|
+
kind: 'resident-agenda',
|
|
64
|
+
current: 6,
|
|
65
|
+
migrations: {
|
|
66
|
+
1: (record) => record,
|
|
67
|
+
2: (record) => record,
|
|
68
|
+
3: (record) => record,
|
|
69
|
+
4: (record) => record,
|
|
70
|
+
5: (record) => record,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const earlierFieldsSchema = z.object({
|
|
75
|
+
tenantId: z.string().uuid(),
|
|
76
|
+
agentKey: z.string().min(1).max(200),
|
|
77
|
+
revision: z.number().int().positive().safe(),
|
|
78
|
+
learning: learningSchema.optional(),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/** Marker written beside the revisions once an earlier store has been carried over. */
|
|
82
|
+
const ADOPTED_MARKER = 'adopted.json'
|
|
83
|
+
const REVISION_FILE = /^([1-9][0-9]{0,15})\.json$/
|
|
84
|
+
|
|
85
|
+
/** A key as one bounded path segment, the way the earlier store addressed it. */
|
|
86
|
+
function keySegment(key: string): string {
|
|
87
|
+
const encoded = revisionFileSegment(key)
|
|
88
|
+
return encoded.length <= 255
|
|
89
|
+
? encoded
|
|
90
|
+
: `~sha256-${createHash('sha256').update(encoded).digest('hex')}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Where guidance was kept before it had a store of its own, for one scope:
|
|
95
|
+
* `<root>/<tenant>/<key>/agenda`. Pass the result as `adoptFrom` to carry it over.
|
|
96
|
+
*/
|
|
97
|
+
export function earlierGuidanceDirectory(
|
|
98
|
+
root: string,
|
|
99
|
+
scope: { readonly tenantId: string; readonly agentKey: string },
|
|
100
|
+
): string {
|
|
101
|
+
return join(root, asTenantId(scope.tenantId), keySegment(scope.agentKey), 'agenda')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @experimental Where a guidance store lives and what it may carry over on first use. */
|
|
105
|
+
export interface DiskLearningGuidanceOptions {
|
|
106
|
+
/**
|
|
107
|
+
* A directory holding guidance in the earlier record format
|
|
108
|
+
* (see {@link earlierGuidanceDirectory}). On the first use of this store,
|
|
109
|
+
* every revision found there is copied into it with its revision number, and
|
|
110
|
+
* a marker is written beside them. The earlier files are only read, never
|
|
111
|
+
* changed or removed, so an older version keeps working on them.
|
|
112
|
+
*/
|
|
113
|
+
readonly adoptFrom?: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @experimental Revisioned, evaluated guidance for one tenant and one scope key,
|
|
118
|
+
* on a trusted local filesystem. Every change is an immutable revision published
|
|
119
|
+
* exclusively, so two processes cannot both commit the same one, and an older
|
|
120
|
+
* revision stays readable to roll one skill back.
|
|
121
|
+
*/
|
|
122
|
+
export class DiskLearningGuidance {
|
|
123
|
+
private readonly records = new DiskRevisionRecordStore<LearningGuidanceState>(
|
|
124
|
+
guidanceRecordSchema,
|
|
125
|
+
'learning guidance',
|
|
126
|
+
(record) => record.revision,
|
|
127
|
+
)
|
|
128
|
+
private readonly historyRecords = new DiskRecordStore<LearningGuidanceState>(guidanceRecordSchema)
|
|
129
|
+
private readonly location
|
|
130
|
+
private readonly tenantId: TenantId
|
|
131
|
+
private readonly agentKey: string
|
|
132
|
+
private readonly directory: string
|
|
133
|
+
private readonly adoptFrom: string | undefined
|
|
134
|
+
private adopting: Promise<void> | undefined
|
|
135
|
+
|
|
136
|
+
constructor(
|
|
137
|
+
directory: string,
|
|
138
|
+
scope: { tenantId: TenantId | string; agentKey: string },
|
|
139
|
+
options: DiskLearningGuidanceOptions = {},
|
|
140
|
+
) {
|
|
141
|
+
this.tenantId = asTenantId(scope.tenantId)
|
|
142
|
+
this.agentKey = z.string().min(1).max(200).parse(scope.agentKey)
|
|
143
|
+
this.directory = directory
|
|
144
|
+
this.adoptFrom = options.adoptFrom
|
|
145
|
+
this.location = {
|
|
146
|
+
legacyPath: join(directory, 'state.json'),
|
|
147
|
+
revisionsDir: join(directory, 'revisions'),
|
|
148
|
+
publishLegacyProjection: false,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private checked(record: LearningGuidanceState): LearningGuidanceState {
|
|
153
|
+
const state = guidanceSchema.parse(record)
|
|
154
|
+
if (state.tenantId !== this.tenantId || state.agentKey !== this.agentKey)
|
|
155
|
+
throw new Error('Guidance does not match the bound tenant and scope key.')
|
|
156
|
+
return Object.freeze({
|
|
157
|
+
...state,
|
|
158
|
+
...(state.learning ? { learning: freezeLearning(state.learning) } : {}),
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Carry an earlier store over once per process, and once ever per directory. */
|
|
163
|
+
private ready(): Promise<void> {
|
|
164
|
+
if (!this.adoptFrom) return Promise.resolve()
|
|
165
|
+
this.adopting ??= this.adopt(this.adoptFrom).catch((error) => {
|
|
166
|
+
this.adopting = undefined
|
|
167
|
+
throw error
|
|
168
|
+
})
|
|
169
|
+
return this.adopting
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async adopt(from: string): Promise<void> {
|
|
173
|
+
const marker = new DiskRecordStore<unknown>(guidanceRecordSchema)
|
|
174
|
+
if ((await marker.read(join(this.directory, ADOPTED_MARKER))) !== null) return
|
|
175
|
+
const earlier = new DiskRecordStore<unknown>(earlierRecordSchema)
|
|
176
|
+
let names: string[]
|
|
177
|
+
try {
|
|
178
|
+
names = await readdir(join(from, 'revisions'))
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return
|
|
181
|
+
throw error
|
|
182
|
+
}
|
|
183
|
+
const revisions = names
|
|
184
|
+
.flatMap((name) => {
|
|
185
|
+
const match = REVISION_FILE.exec(name)
|
|
186
|
+
return match ? [Number(match[1])] : []
|
|
187
|
+
})
|
|
188
|
+
.sort((a, b) => a - b)
|
|
189
|
+
if (!revisions.length) return
|
|
190
|
+
const head = revisions[revisions.length - 1] as number
|
|
191
|
+
if (revisions.length !== head)
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Earlier guidance in ${from} has gaps between revisions 1 and ${head}; it was not carried over.`,
|
|
194
|
+
)
|
|
195
|
+
for (const revision of revisions) {
|
|
196
|
+
const current = await this.records.read(this.location)
|
|
197
|
+
if ((current?.revision ?? 0) >= revision) continue
|
|
198
|
+
const raw = await earlier.read(join(from, 'revisions', `${revision}.json`))
|
|
199
|
+
const fields = earlierFieldsSchema.parse(raw)
|
|
200
|
+
if (
|
|
201
|
+
fields.revision !== revision ||
|
|
202
|
+
fields.tenantId !== this.tenantId ||
|
|
203
|
+
fields.agentKey !== this.agentKey
|
|
204
|
+
)
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Earlier guidance revision ${revision} in ${from} does not match this tenant and scope key; it was not carried over.`,
|
|
207
|
+
)
|
|
208
|
+
const record = this.checked({
|
|
209
|
+
tenantId: fields.tenantId,
|
|
210
|
+
agentKey: fields.agentKey,
|
|
211
|
+
revision,
|
|
212
|
+
...(fields.learning ? { learning: fields.learning } : {}),
|
|
213
|
+
})
|
|
214
|
+
try {
|
|
215
|
+
await this.records.transact(this.location, (latest) => {
|
|
216
|
+
if ((latest?.revision ?? 0) !== revision - 1) throw new LearningGuidanceConflictError()
|
|
217
|
+
return { record, result: undefined }
|
|
218
|
+
})
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// A concurrent process carrying over the same revision wins identically.
|
|
221
|
+
const after = await this.records.read(this.location)
|
|
222
|
+
if ((after?.revision ?? 0) < revision) throw error
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await marker.write(join(this.directory, ADOPTED_MARKER), {
|
|
226
|
+
from,
|
|
227
|
+
revisions: head,
|
|
228
|
+
adoptedAt: new Date().toISOString(),
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async read(): Promise<LearningGuidanceState | null> {
|
|
233
|
+
await this.ready()
|
|
234
|
+
const record = await this.records.read(this.location)
|
|
235
|
+
return record === null ? null : this.checked(record)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** One immutable revision, as it was committed. */
|
|
239
|
+
async readRevision(revision: number): Promise<LearningGuidanceState | null> {
|
|
240
|
+
z.number().int().positive().safe().parse(revision)
|
|
241
|
+
await this.ready()
|
|
242
|
+
const record = await this.historyRecords.read(
|
|
243
|
+
join(this.location.revisionsDir, `${revision}.json`),
|
|
244
|
+
)
|
|
245
|
+
if (record === null) return null
|
|
246
|
+
const state = this.checked(record)
|
|
247
|
+
if (state.revision !== revision)
|
|
248
|
+
throw new Error('Guidance revision filename and body disagree.')
|
|
249
|
+
return state
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Create the empty first revision. A second creator gets a conflict. */
|
|
253
|
+
async create(): Promise<LearningGuidanceState> {
|
|
254
|
+
await this.ready()
|
|
255
|
+
const record = this.checked({
|
|
256
|
+
tenantId: this.tenantId,
|
|
257
|
+
agentKey: this.agentKey,
|
|
258
|
+
revision: 1,
|
|
259
|
+
})
|
|
260
|
+
return this.records.transact(this.location, (current) => {
|
|
261
|
+
if (current !== null) throw new LearningGuidanceConflictError()
|
|
262
|
+
return { record, result: record }
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The current guidance, created empty when none exists yet. Safe to race. */
|
|
267
|
+
async open(): Promise<LearningGuidanceState> {
|
|
268
|
+
const existing = await this.read()
|
|
269
|
+
if (existing) return existing
|
|
270
|
+
try {
|
|
271
|
+
return await this.create()
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const raced = await this.read()
|
|
274
|
+
if (!raced) throw error
|
|
275
|
+
return raced
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private async learn(
|
|
280
|
+
expected: LearningGuidanceState,
|
|
281
|
+
learning: LearningState,
|
|
282
|
+
): Promise<LearningGuidanceState> {
|
|
283
|
+
await this.ready()
|
|
284
|
+
const snapshot = this.checked(expected)
|
|
285
|
+
return this.records.transact(this.location, (record) => {
|
|
286
|
+
if (record === null) throw new LearningGuidanceConflictError()
|
|
287
|
+
const current = this.checked(record)
|
|
288
|
+
if (current.revision !== snapshot.revision) throw new LearningGuidanceConflictError()
|
|
289
|
+
if (!isDeepStrictEqual(current.learning, snapshot.learning))
|
|
290
|
+
throw new LearningGuidanceConflictError()
|
|
291
|
+
const next = this.checked({ ...current, learning, revision: current.revision + 1 })
|
|
292
|
+
return { record: next, result: next }
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Append a host-evidenced correction to identity or preferences. */
|
|
297
|
+
async updateProfile(
|
|
298
|
+
expected: LearningGuidanceState,
|
|
299
|
+
input: LearningProfileUpdate,
|
|
300
|
+
): Promise<LearningGuidanceState> {
|
|
301
|
+
const snapshot = this.checked(expected)
|
|
302
|
+
return this.learn(snapshot, reviseLearningProfile(snapshot.learning, input))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Bind evaluated guidance to the current learning version. */
|
|
306
|
+
async promoteSkill(
|
|
307
|
+
expected: LearningGuidanceState,
|
|
308
|
+
candidate: LearningSkillCandidate,
|
|
309
|
+
evaluation: LearningSkillEvaluation,
|
|
310
|
+
evidence: LearningEvidence,
|
|
311
|
+
): Promise<LearningGuidanceState> {
|
|
312
|
+
const snapshot = this.checked(expected)
|
|
313
|
+
return this.learn(
|
|
314
|
+
snapshot,
|
|
315
|
+
promoteLearningSkill(snapshot.learning, candidate, evaluation, evidence),
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Restore one skill from an immutable earlier revision while keeping the current profile. */
|
|
320
|
+
async rollbackSkill(
|
|
321
|
+
expected: LearningGuidanceState,
|
|
322
|
+
name: string,
|
|
323
|
+
fromRevision: number,
|
|
324
|
+
evidence: LearningEvidence,
|
|
325
|
+
): Promise<LearningGuidanceState> {
|
|
326
|
+
const snapshot = this.checked(expected)
|
|
327
|
+
const checkedEvidence = learningEvidenceSchema.parse(evidence)
|
|
328
|
+
z.number().int().positive().safe().max(snapshot.revision).parse(fromRevision)
|
|
329
|
+
z.string()
|
|
330
|
+
.regex(/^[a-z0-9][a-z0-9_-]{0,63}$/)
|
|
331
|
+
.parse(name)
|
|
332
|
+
const historical = await this.readRevision(fromRevision)
|
|
333
|
+
if (!historical) throw new Error('Skill rollback revision is missing.')
|
|
334
|
+
return this.learn(
|
|
335
|
+
snapshot,
|
|
336
|
+
restoreLearningSkill(snapshot.learning, historical.learning, name, checkedEvidence),
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
}
|
package/src/learning/skill.ts
CHANGED
|
@@ -119,11 +119,11 @@ export const learningSchema = z
|
|
|
119
119
|
)
|
|
120
120
|
context.addIssue({
|
|
121
121
|
code: z.ZodIssueCode.custom,
|
|
122
|
-
message: 'Invalid
|
|
122
|
+
message: 'Invalid learning identity or content digest.',
|
|
123
123
|
})
|
|
124
124
|
})
|
|
125
125
|
|
|
126
|
-
/** @experimental Versioned behavioral overlay; the original
|
|
126
|
+
/** @experimental Versioned behavioral overlay; the original identity remains unchanged. */
|
|
127
127
|
export interface LearningState {
|
|
128
128
|
readonly revision: number
|
|
129
129
|
readonly identity?: { readonly text: string; readonly evidence: LearningEvidence }
|
|
@@ -219,7 +219,7 @@ function base(current: LearningState | undefined, evidence: LearningEvidence): L
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
/** Internal pure profile transition; storage owns exact
|
|
222
|
+
/** Internal pure profile transition; storage owns the exact-revision check. */
|
|
223
223
|
export function reviseLearningProfile(
|
|
224
224
|
current: LearningState | undefined,
|
|
225
225
|
input: LearningProfileUpdate,
|
|
@@ -306,7 +306,7 @@ export function promoteLearningSkill(
|
|
|
306
306
|
batch.candidate.length > 128 ||
|
|
307
307
|
batch.attributions.length > 128
|
|
308
308
|
)
|
|
309
|
-
throw new Error('
|
|
309
|
+
throw new Error('Skill evaluation is bounded to 64 tasks per round.')
|
|
310
310
|
// Recorded evidence must consistently identify success and complete measurements.
|
|
311
311
|
for (const trial of [...batch.baseline, ...batch.candidate]) {
|
|
312
312
|
if (
|
|
@@ -322,7 +322,7 @@ export function promoteLearningSkill(
|
|
|
322
322
|
protection,
|
|
323
323
|
)
|
|
324
324
|
if (review.decision !== 'accept')
|
|
325
|
-
throw new Error(`
|
|
325
|
+
throw new Error(`Skill promotion ${review.decision}: ${review.reason}`)
|
|
326
326
|
const skill: LearnedSkill = {
|
|
327
327
|
...candidate,
|
|
328
328
|
hash,
|
|
@@ -358,7 +358,7 @@ export function restoreLearningSkill(
|
|
|
358
358
|
): LearningState {
|
|
359
359
|
name.parse(skillName)
|
|
360
360
|
if (!current?.skills.some((s) => s.name === skillName))
|
|
361
|
-
throw new Error('No active
|
|
361
|
+
throw new Error('No active skill to roll back.')
|
|
362
362
|
const next = base(current, evidenceFor(current, inputEvidence))
|
|
363
363
|
const previous = historical
|
|
364
364
|
? freezeLearning(historical).skills.find((s) => s.name === skillName)
|
|
@@ -398,7 +398,7 @@ export function projectLearning(
|
|
|
398
398
|
options: LearningProjectionOptions,
|
|
399
399
|
): LearningProjection {
|
|
400
400
|
if (!Number.isSafeInteger(options.maxChars) || options.maxChars < 0 || options.maxChars > 64_000)
|
|
401
|
-
throw new TypeError('
|
|
401
|
+
throw new TypeError('Learning context requires maxChars between 0 and 64000.')
|
|
402
402
|
const selected = z.array(name).max(16).parse(options.skillNames)
|
|
403
403
|
const purpose = z.enum(['task', 'exploration']).parse(options.purpose ?? 'task')
|
|
404
404
|
if (new Set(selected).size !== selected.length) throw new Error('Duplicate selected skill names.')
|
package/src/learning/store.ts
CHANGED
|
@@ -286,7 +286,7 @@ export class SqliteLearningStore {
|
|
|
286
286
|
row.project_id !== this.scope.projectId ||
|
|
287
287
|
row.agent_key !== this.scope.agentKey
|
|
288
288
|
)
|
|
289
|
-
throw new Error('Learning cycle does not belong to this tenant, project and
|
|
289
|
+
throw new Error('Learning cycle does not belong to this tenant, project and scope key.')
|
|
290
290
|
return row
|
|
291
291
|
}
|
|
292
292
|
|
|
@@ -524,7 +524,7 @@ export class SqliteLearningStore {
|
|
|
524
524
|
(event.data.tenantId !== this.scope.tenantId ||
|
|
525
525
|
event.data.agentKey !== this.scope.agentKey)
|
|
526
526
|
)
|
|
527
|
-
throw new Error('Learning start does not match the bound
|
|
527
|
+
throw new Error('Learning start does not match the bound scope key.')
|
|
528
528
|
const parent =
|
|
529
529
|
event.data.parentCycleId === undefined ? null : uuid.parse(event.data.parentCycleId)
|
|
530
530
|
if (parent && !this.owned(db, parent))
|
|
@@ -843,11 +843,10 @@ export async function runStoredLearningFromObservations(
|
|
|
843
843
|
options: LearningDiscoveryOptions,
|
|
844
844
|
): Promise<LearningDiscoveryResult> {
|
|
845
845
|
options.signal.throwIfAborted()
|
|
846
|
-
const
|
|
847
|
-
if (!
|
|
848
|
-
throw new Error('Learning discovery requires an unpaused agenda without running pursuits.')
|
|
846
|
+
const guidance = await options.guidance.read()
|
|
847
|
+
if (!guidance) throw new Error('Learning discovery requires existing learning guidance.')
|
|
849
848
|
const targets = options.evaluators.map((target) => {
|
|
850
|
-
const skill =
|
|
849
|
+
const skill = guidance.learning?.skills.find((s) => s.name === target.skillName)
|
|
851
850
|
return { ...target, baselineRevision: skill ? hashLearningSkill(skill) : 'none' }
|
|
852
851
|
})
|
|
853
852
|
const observation = await store.selectObservation(targets)
|
package/src/peers/client.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A client for one connection of `namzu-peer/1`: connect, write one request
|
|
3
3
|
* line, read one response line, close. Mirrors the shape of this
|
|
4
|
-
* repository's other local control clients (`
|
|
5
|
-
* `packages/cli/src/
|
|
6
|
-
* `callEndpoint` in `packages/cli/src/schedule/daemon/endpoint.ts`): an
|
|
4
|
+
* repository's other local control clients (`callEndpoint` in
|
|
5
|
+
* `packages/cli/src/schedule/daemon/endpoint.ts`): an
|
|
7
6
|
* authenticated response PROVES responsiveness, and its absence never proves
|
|
8
7
|
* the peer is dead — only that this attempt could not reach it.
|
|
9
8
|
*
|
package/src/peers/endpoint.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* session, newline-delimited JSON framing, a closed op set (design §1.1,
|
|
4
4
|
* §1.3). Mirrors the shape of this repository's other local control
|
|
5
5
|
* endpoints (`startEndpoint` in
|
|
6
|
-
* `packages/cli/src/schedule/daemon/endpoint.ts
|
|
7
|
-
* in `packages/cli/src/integrations/resident/runner-control.ts`): a total
|
|
6
|
+
* `packages/cli/src/schedule/daemon/endpoint.ts`): a total
|
|
8
7
|
* read deadline per connection (not an inactivity timer a slow sender can
|
|
9
8
|
* reset), a bounded connection count, and a destroyed socket rather than an
|
|
10
9
|
* error response for anything malformed — request bytes and tokens are
|
package/src/peers/protocol.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const DEFAULT_PEER_READ_DEADLINE_MS = 2_000
|
|
|
36
36
|
/** Concurrent connections a `createPeerEndpoint` server accepts before refusing new ones. */
|
|
37
37
|
export const MAX_PEER_CONNECTIONS = 16
|
|
38
38
|
|
|
39
|
+
// 'resident' is no longer announced; it stays so an older peer's record still parses.
|
|
39
40
|
export const PeerSessionKindSchema = z.enum(['tui', 'exec', 'resident', 'scheduled'])
|
|
40
41
|
export type PeerSessionKind = z.infer<typeof PeerSessionKindSchema>
|
|
41
42
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `CODING_AGENT_WORKING_DOCTRINE` names only the builtins and is meant for the
|
|
22
22
|
* parent and for every delegated sub-agent, because a delegated task edits the
|
|
23
23
|
* same repository under the same rules. `CODING_AGENT_DELEGATION_DOCTRINE`
|
|
24
|
-
* names `
|
|
24
|
+
* names `Agent` and speaks of the task list, which a sub-agent does not have, and so is
|
|
25
25
|
* for the parent only. An adversarial review caught a draft ordering children
|
|
26
26
|
* to plan with a tool they could not call; a rule about a tool the reader does
|
|
27
27
|
* not have is not guidance, it is an instruction to fail.
|
|
@@ -38,6 +38,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
|
|
|
38
38
|
|
|
39
39
|
### Delivering work
|
|
40
40
|
- Act when you have enough information. Take established facts and the user's decisions as settled.
|
|
41
|
+
- Begin editing once you know the first change, and refine as you go. If you stop before changing anything, the next step starts from the same code.
|
|
41
42
|
- The requested scope is the deliverable: deliver it at the size and on the subject it was asked. Read ambiguity the way a careful colleague would; make the routine judgment calls yourself and check in only when different readings would produce materially different work.
|
|
42
43
|
- Finish the whole task, not just the easy parts. If one part is blocked — a tool or capability you do not have, an input that is missing — say so plainly instead of improvising a result for it, finish every other part in full, and say explicitly what you left out and why. Scaling the work down is the user's call.
|
|
43
44
|
- If you find a real problem with the task as stated, say so in a sentence or two, then keep going under a stated assumption. Reserve blocking questions for cases where proceeding under any assumption would be unsafe or would make the work useless if wrong.
|
|
@@ -59,6 +60,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
|
|
|
59
60
|
- Before editing, use the relevant file content already available from a successful read or write; if it is unavailable or may be stale, read the needed portion. Read additional surrounding code when needed to match its naming and idiom. Respect project instructions and tool-specific prerequisites even when they require a fresh read.
|
|
60
61
|
- Prefer the dedicated tools over shell equivalents (\`read\` over \`cat\`, \`grep\` over a shell grep, \`glob\` over \`find\`, \`edit\` over \`sed -i\`, \`write\` over a shell redirect), because their structured inputs and focused results are easier to check. Use \`bash\` when the task needs a shell; permissions apply the same way.
|
|
61
62
|
- Independent tool calls go in one response: reading three files, or running a grep and a glob, does not need three turns. Wait for a mutation to finish before an observation or another action that depends on it; runtime ordering and concurrency limits remain authoritative.
|
|
63
|
+
- For a defect, reproduce it first with a small script or a failing test, and keep it. Make the change, then rerun that reproduction. Afterwards run the project's tests and its lint or format check, add tests for behaviour you introduced, and say where any throwaway file you made is. Say in your report what you ran and what you did not run.
|
|
62
64
|
- Match verification to the task and its risk. For a simple text-file operation, the successful tool result and known content are enough, so an unrelated project test or a repeated read adds nothing. For code changes, run the relevant tests, typecheck or lint that would catch a mistake, and follow required project checks. Claim only the checks you ran.
|
|
63
65
|
- Describe the state you leave the working tree in: say where scratch files are, and which files outside the request you touched.
|
|
64
66
|
|
|
@@ -74,11 +76,11 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
|
|
|
74
76
|
- Keep the final reply short and specific: what changed, what was verified, what is left.`
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
|
-
* Parent-only: rules about
|
|
79
|
+
* Parent-only: rules about the task list and `Agent`, which a
|
|
78
80
|
* delegated sub-agent does not have. See the module comment for the split.
|
|
79
81
|
*/
|
|
80
82
|
export const CODING_AGENT_DELEGATION_DOCTRINE = `### Planning and delegating
|
|
81
|
-
- For work of three or more steps — several files, several
|
|
83
|
+
- A task list is optional. For work of three or more distinct steps — several files, several separate stages — you may keep one: exactly one step marked in progress at a time, each marked done when it is done rather than at the end, and the list cleared away once everything is done. Do not make one for a single inspect-edit-test cycle or a two-step job; a list of one or two items tells the user nothing. Keeping a list is not progress on the task itself.
|
|
82
84
|
- When the \`Agent\` tool is available, delegate independent work through it and give each delegation a short, specific description; the user watches those descriptions, not the prompts. Brief each sub-agent with four things: the objective; what is out of bounds; which tools and sources to use; and the shape of the reply you want, such as a short summary with a file path for anything long. A fresh sub-agent starts with none of your context and returns what you ask for.
|
|
83
85
|
- When the \`Agent\` tool lists a \`fork\` type, use \`subagent_type: "fork"\` when a side task would fill your context with output you will not need again, such as an open-ended survey. A fork starts from this conversation under your own instructions, so its prompt is only the directive: what to do and where its scope ends, without the background. Never write or guess what a fork will find before its report arrives. A fork cannot start forks of its own.
|
|
84
86
|
- When delegated agents finish, tell the user what each one found in your next message. Their results reach you and not the user, so a result you do not pass on stays unseen.
|
package/src/prompt/index.ts
CHANGED
|
@@ -23,7 +23,3 @@ export {
|
|
|
23
23
|
codingAgentDoctrineContribution,
|
|
24
24
|
} from './coding-agent-doctrine.js'
|
|
25
25
|
export type { CodingAgentDoctrineOptions } from './coding-agent-doctrine.js'
|
|
26
|
-
export { createResidentStepContributions } from './resident-step.js'
|
|
27
|
-
export type { ResidentStepPromptOptions } from './resident-step.js'
|
|
28
|
-
export { createResidentStepContext } from './resident-learning.js'
|
|
29
|
-
export type { ResidentStepContextOptions, ResidentStepContextBundle } from './resident-learning.js'
|
package/src/provider/fallback.ts
CHANGED
|
@@ -554,6 +554,13 @@ export function withProviderFallback(
|
|
|
554
554
|
const serving = members[cursor] ?? first
|
|
555
555
|
return serving.provider.supportsImageInputFor?.(serving.model ?? model) ?? true
|
|
556
556
|
},
|
|
557
|
+
// A chain takes a forced choice only if every member does: the request may
|
|
558
|
+
// be served by any of them, and the one that refuses would fail the step.
|
|
559
|
+
acceptsForcedToolChoiceFor: (model: string, thinking?: ThinkingConfig) =>
|
|
560
|
+
members.every(
|
|
561
|
+
(member) =>
|
|
562
|
+
member.provider.acceptsForcedToolChoiceFor?.(member.model ?? model, thinking) ?? true,
|
|
563
|
+
),
|
|
557
564
|
reasoningEffortLevelsFor: (model: string, thinking?: ThinkingConfig) =>
|
|
558
565
|
commonReasoningEffortLevels(members, model, thinking),
|
|
559
566
|
// Present on the chain wrapper for the same reason the menu method is:
|
|
@@ -223,6 +223,14 @@ export function withStreamIdleTimeout(
|
|
|
223
223
|
supportsImageInputFor: (model: string) => provider.supportsImageInputFor?.(model) ?? true,
|
|
224
224
|
}
|
|
225
225
|
: {}),
|
|
226
|
+
...(provider.acceptsForcedToolChoiceFor
|
|
227
|
+
? {
|
|
228
|
+
acceptsForcedToolChoiceFor: (
|
|
229
|
+
model: string,
|
|
230
|
+
thinking?: Parameters<NonNullable<LLMProvider['acceptsForcedToolChoiceFor']>>[1],
|
|
231
|
+
) => provider.acceptsForcedToolChoiceFor?.(model, thinking) ?? true,
|
|
232
|
+
}
|
|
233
|
+
: {}),
|
|
226
234
|
...(provider.reasoningEffortLevelsFor
|
|
227
235
|
? {
|
|
228
236
|
reasoningEffortLevelsFor: (
|
package/src/provider/retry.ts
CHANGED
|
@@ -384,6 +384,14 @@ export function withProviderRetry(
|
|
|
384
384
|
supportsImageInputFor: (model: string) => provider.supportsImageInputFor?.(model) ?? true,
|
|
385
385
|
}
|
|
386
386
|
: {}),
|
|
387
|
+
...(provider.acceptsForcedToolChoiceFor
|
|
388
|
+
? {
|
|
389
|
+
acceptsForcedToolChoiceFor: (
|
|
390
|
+
model: string,
|
|
391
|
+
thinking?: Parameters<NonNullable<LLMProvider['acceptsForcedToolChoiceFor']>>[1],
|
|
392
|
+
) => provider.acceptsForcedToolChoiceFor?.(model, thinking) ?? true,
|
|
393
|
+
}
|
|
394
|
+
: {}),
|
|
387
395
|
...(provider.reasoningEffortLevelsFor
|
|
388
396
|
? {
|
|
389
397
|
reasoningEffortLevelsFor: (
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reason a step is stopped on purpose, by the loop that is reading it.
|
|
3
|
+
*
|
|
4
|
+
* A step that has spent its whole reasoning allowance is cut short: the loop
|
|
5
|
+
* stops reading the stream and aborts the request it opened. That abort is the
|
|
6
|
+
* loop's own decision, not a caller's stop and not a transport failure, so it
|
|
7
|
+
* carries this class as its reason. A provider wrapper that sees an aborted
|
|
8
|
+
* request can tell the two apart with {@link isStepStopped} and must not retry
|
|
9
|
+
* it, fail over from it, or report it as an error.
|
|
10
|
+
*/
|
|
11
|
+
export class StepStopped extends Error {
|
|
12
|
+
override readonly name = 'StepStopped'
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super('the step was stopped by the loop that was reading it')
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Whether an abort reason, or an error that carries one, is a deliberate step stop. */
|
|
20
|
+
export function isStepStopped(value: unknown): value is StepStopped {
|
|
21
|
+
return value instanceof StepStopped
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Whether a request signal was aborted by a deliberate step stop. */
|
|
25
|
+
export function stepWasStopped(signal: AbortSignal | undefined): boolean {
|
|
26
|
+
return signal?.aborted === true && isStepStopped(signal.reason)
|
|
27
|
+
}
|
|
@@ -6,6 +6,7 @@ import { ProviderError, classifyProviderError } from '../types/provider/errors.j
|
|
|
6
6
|
import type { LLMProvider, StreamChunk } from '../types/provider/index.js'
|
|
7
7
|
import { cancelCauseOf } from '../types/session/cancel-cause.js'
|
|
8
8
|
import { isProviderRequestError } from './errors.js'
|
|
9
|
+
import { isStepStopped } from './step-stopped.js'
|
|
9
10
|
|
|
10
11
|
export class TokenBudgetAdmissionError extends ProviderError {
|
|
11
12
|
constructor() {
|
|
@@ -117,6 +118,7 @@ export function withTokenBudget(provider: LLMProvider, budget: SessionTokenBudge
|
|
|
117
118
|
}
|
|
118
119
|
if (Object.keys(chunk.delta).length > 0) outputObserved = true
|
|
119
120
|
if (typeof chunk.delta.content === 'string') streamedChars += chunk.delta.content.length
|
|
121
|
+
streamedChars += chunk.delta.reasoning?.text?.length ?? 0
|
|
120
122
|
if (chunk.error) throw new Error(chunk.error)
|
|
121
123
|
yield chunk
|
|
122
124
|
}
|
|
@@ -161,10 +163,13 @@ export function withTokenBudget(provider: LLMProvider, budget: SessionTokenBudge
|
|
|
161
163
|
// because of work somebody chose to stop. The estimate can under-count
|
|
162
164
|
// what the provider billed for the part it never reported; a failure
|
|
163
165
|
// that was not a cancellation keeps its marker as before.
|
|
166
|
+
//
|
|
167
|
+
// A step the loop stopped on purpose (it reached its reasoning cap) is
|
|
168
|
+
// closed the same way: nobody chose to lose track of that spend.
|
|
164
169
|
if (
|
|
165
170
|
usage === undefined &&
|
|
166
171
|
signal?.aborted === true &&
|
|
167
|
-
cancelCauseOf(signal.reason) === 'user' &&
|
|
172
|
+
(cancelCauseOf(signal.reason) === 'user' || isStepStopped(signal.reason)) &&
|
|
168
173
|
!uncertainRetry
|
|
169
174
|
) {
|
|
170
175
|
const prompt = estimateMessagesTokens(params.messages)
|