@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21
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 +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import type { AgentRole } from '../agents/role-results'
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MODEL,
|
|
6
|
+
ModelNameSchema,
|
|
7
|
+
modelsOf,
|
|
8
|
+
ROUTED_ROLES,
|
|
9
|
+
type EngineConfig,
|
|
10
|
+
type ModelName,
|
|
11
|
+
type ModelsConfig,
|
|
12
|
+
} from '../config/engine-config'
|
|
13
|
+
import type { JournalRecord } from '../journal/journal-record'
|
|
14
|
+
import type { RunState } from '../journal/replay'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Which model each fresh agent launches on (#603): the pick rule, and the
|
|
18
|
+
* decision model's answer it reads from the journal.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Why an agent launched on its model:
|
|
23
|
+
* - `locked`: a reviewer, a lens, or a final-review fixer, always Opus.
|
|
24
|
+
* - `after_failure`: its role already failed a try on its ticket on a
|
|
25
|
+
* cheaper model (#604).
|
|
26
|
+
* - `floor`: its role's floor is Opus.
|
|
27
|
+
* - `no_answer`: the decision model gave no answer (off, timed out,
|
|
28
|
+
* errored, or the ask was skipped).
|
|
29
|
+
* - `unsure`: its confidence was below the bar or missing, or it picked a
|
|
30
|
+
* model the ask didn't offer.
|
|
31
|
+
* - `decision_model`: the decision model's pick.
|
|
32
|
+
*/
|
|
33
|
+
export const ModelReasonSchema = z.enum([
|
|
34
|
+
'locked',
|
|
35
|
+
'after_failure',
|
|
36
|
+
'floor',
|
|
37
|
+
'no_answer',
|
|
38
|
+
'unsure',
|
|
39
|
+
'decision_model',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
export type ModelReason = z.infer<typeof ModelReasonSchema>
|
|
43
|
+
|
|
44
|
+
/** The model an agent launches on, and why. */
|
|
45
|
+
export type ModelPick = { model: ModelName; model_reason: ModelReason }
|
|
46
|
+
|
|
47
|
+
/** Reviewers, lenses, and the final review's fixers: always Opus. */
|
|
48
|
+
export const LOCKED_PICK: ModelPick = {
|
|
49
|
+
model: DEFAULT_MODEL,
|
|
50
|
+
model_reason: 'locked',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RoutedRole = (typeof ROUTED_ROLES)[number]
|
|
54
|
+
|
|
55
|
+
/** Whether a role has a model floor, so the decision model may pick its model. */
|
|
56
|
+
export const isRoutedRole = (role: string): role is RoutedRole =>
|
|
57
|
+
ROUTED_ROLES.some((routed) => routed === role)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The models an `agent_model` ask offers for a role with this floor: the
|
|
61
|
+
* floor and every stronger one. A floor of `opus` offers all three, since
|
|
62
|
+
* that ask is shadow only.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* offeredModels({ floor: 'sonnet' }) // ['opus', 'sonnet']
|
|
66
|
+
*/
|
|
67
|
+
export const offeredModels = ({ floor }: { floor: ModelName }): ModelName[] => {
|
|
68
|
+
const all = ModelNameSchema.options
|
|
69
|
+
return floor === 'opus' ? [...all] : all.slice(0, all.indexOf(floor) + 1)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The decision model's answer to an `agent_model` ask, and what it offered. */
|
|
73
|
+
export type ModelAnswer = {
|
|
74
|
+
offered: string[]
|
|
75
|
+
pick: string | number | null
|
|
76
|
+
confidence: number | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The model a fresh agent launches on. Pure; the first match wins: a
|
|
81
|
+
* reviewer or lens is `locked`; a role that failed a try on its ticket on a
|
|
82
|
+
* cheaper model is `after_failure`; a floor of `opus` is `floor`; no answer
|
|
83
|
+
* is `no_answer`; an answer below the bar, with no confidence, or not
|
|
84
|
+
* offered is `unsure`; else the decision model's pick. Every fallback is
|
|
85
|
+
* Opus.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* pickModel({ role: 'test-writer', models, failed_cheaper: false, answer: { offered: ['sonnet', 'opus'], pick: 'sonnet', confidence: 0.9 } })
|
|
89
|
+
* // { model: 'sonnet', model_reason: 'decision_model' } with a sonnet floor and the 0.8 bar
|
|
90
|
+
*/
|
|
91
|
+
export const pickModel = ({
|
|
92
|
+
role,
|
|
93
|
+
models,
|
|
94
|
+
failed_cheaper,
|
|
95
|
+
answer,
|
|
96
|
+
}: {
|
|
97
|
+
role: AgentRole
|
|
98
|
+
models: ModelsConfig
|
|
99
|
+
/** Whether the role already failed a try on its ticket on a cheaper model. */
|
|
100
|
+
failed_cheaper: boolean
|
|
101
|
+
answer: ModelAnswer | null
|
|
102
|
+
}): ModelPick => {
|
|
103
|
+
if (!isRoutedRole(role)) return LOCKED_PICK
|
|
104
|
+
if (failed_cheaper) return { model: 'opus', model_reason: 'after_failure' }
|
|
105
|
+
if (models.floors[role] === 'opus') {
|
|
106
|
+
return { model: 'opus', model_reason: 'floor' }
|
|
107
|
+
}
|
|
108
|
+
if (answer === null) return { model: 'opus', model_reason: 'no_answer' }
|
|
109
|
+
const pick = ModelNameSchema.safeParse(answer.pick)
|
|
110
|
+
if (
|
|
111
|
+
!pick.success ||
|
|
112
|
+
!answer.offered.includes(pick.data) ||
|
|
113
|
+
answer.confidence === null ||
|
|
114
|
+
answer.confidence < models.confidence_bar
|
|
115
|
+
) {
|
|
116
|
+
return { model: 'opus', model_reason: 'unsure' }
|
|
117
|
+
}
|
|
118
|
+
return { model: pick.data, model_reason: 'decision_model' }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The answer to the `agent_model` ask journaled for this agent since its
|
|
123
|
+
* role's last start on the ticket, or `null` with none: no ask (the decision
|
|
124
|
+
* model is off, or the ask was skipped), or an ask that failed.
|
|
125
|
+
*/
|
|
126
|
+
export const modelAnswerOf = ({
|
|
127
|
+
records,
|
|
128
|
+
ticket,
|
|
129
|
+
role,
|
|
130
|
+
}: {
|
|
131
|
+
records: JournalRecord[]
|
|
132
|
+
ticket: number | null
|
|
133
|
+
role: AgentRole
|
|
134
|
+
}): ModelAnswer | null => {
|
|
135
|
+
const lastStart = records.findLastIndex(
|
|
136
|
+
(record) =>
|
|
137
|
+
record.kind === 'agent_started' &&
|
|
138
|
+
record.ticket === ticket &&
|
|
139
|
+
record.role === role
|
|
140
|
+
)
|
|
141
|
+
const asked = records
|
|
142
|
+
.slice(lastStart + 1)
|
|
143
|
+
.findLast(
|
|
144
|
+
(record) =>
|
|
145
|
+
record.kind === 'jev_asked' &&
|
|
146
|
+
record.content.job === 'agent_model' &&
|
|
147
|
+
record.ticket === ticket &&
|
|
148
|
+
record.role === role
|
|
149
|
+
)
|
|
150
|
+
if (asked?.kind !== 'jev_asked') return null
|
|
151
|
+
const answered = records.find(
|
|
152
|
+
(record) =>
|
|
153
|
+
record.kind === 'jev_answered' &&
|
|
154
|
+
record.content.asked_seq === asked.seq
|
|
155
|
+
)
|
|
156
|
+
if (answered?.kind !== 'jev_answered') return null
|
|
157
|
+
const answer = answered.content.answers.model
|
|
158
|
+
if (answer === undefined) return null
|
|
159
|
+
const question = asked.content.request.questions.model
|
|
160
|
+
return {
|
|
161
|
+
offered:
|
|
162
|
+
question?.type === 'choice' ? Object.keys(question.criteria) : [],
|
|
163
|
+
pick: answer.value,
|
|
164
|
+
confidence: answer.confidence,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Whether this role failed a try on this ticket on a model cheaper than
|
|
170
|
+
* Opus (#604), as replay counts a try (`failed_cheaper`): the learner's at
|
|
171
|
+
* the end of a run, with no ticket.
|
|
172
|
+
*/
|
|
173
|
+
export const failedCheaper = ({
|
|
174
|
+
state,
|
|
175
|
+
ticket,
|
|
176
|
+
role,
|
|
177
|
+
}: {
|
|
178
|
+
state: RunState
|
|
179
|
+
ticket: number | null
|
|
180
|
+
role: string
|
|
181
|
+
}): boolean =>
|
|
182
|
+
ticket === null
|
|
183
|
+
? role === 'learner' && state.memory.learner.failed_cheaper
|
|
184
|
+
: (state.tickets[ticket]?.failed_cheaper ?? []).some(
|
|
185
|
+
(failed) => failed === role
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The model a launch step's earlier try journaled before a crash cut it
|
|
190
|
+
* off, so its redo launches on it again (#604); `null` if none did.
|
|
191
|
+
*
|
|
192
|
+
* @param first_seq - The seq of the step's first try.
|
|
193
|
+
*/
|
|
194
|
+
export const redonePick = ({
|
|
195
|
+
records,
|
|
196
|
+
first_seq,
|
|
197
|
+
ticket,
|
|
198
|
+
role,
|
|
199
|
+
}: {
|
|
200
|
+
records: JournalRecord[]
|
|
201
|
+
first_seq: number
|
|
202
|
+
ticket: number | null
|
|
203
|
+
role: string
|
|
204
|
+
}): ModelPick | null => {
|
|
205
|
+
const started = records.findLast(
|
|
206
|
+
(record) =>
|
|
207
|
+
record.seq >= first_seq &&
|
|
208
|
+
record.kind === 'agent_started' &&
|
|
209
|
+
record.ticket === ticket &&
|
|
210
|
+
record.content.role === role
|
|
211
|
+
)
|
|
212
|
+
if (started?.kind !== 'agent_started') return null
|
|
213
|
+
const model = ModelNameSchema.safeParse(started.content.model)
|
|
214
|
+
const { model_reason } = started.content
|
|
215
|
+
if (!model.success || model_reason === null) return null
|
|
216
|
+
return { model: model.data, model_reason }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Which try of its step a launch is: a redo names its first try's seq. */
|
|
220
|
+
export type LaunchTry = { first_seq: number; redo: boolean }
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Whether a fresh agent's launch asks the decision model for its model: not
|
|
224
|
+
* after its role failed a try on a cheaper model, and not on a redo whose
|
|
225
|
+
* first try journaled its model.
|
|
226
|
+
*/
|
|
227
|
+
export const asksModel = ({
|
|
228
|
+
records,
|
|
229
|
+
state,
|
|
230
|
+
ticket,
|
|
231
|
+
role,
|
|
232
|
+
step,
|
|
233
|
+
}: {
|
|
234
|
+
records: JournalRecord[]
|
|
235
|
+
state: RunState
|
|
236
|
+
ticket: number | null
|
|
237
|
+
role: string
|
|
238
|
+
step: LaunchTry | undefined
|
|
239
|
+
}): boolean =>
|
|
240
|
+
!(
|
|
241
|
+
(step?.redo === true &&
|
|
242
|
+
redonePick({ records, first_seq: step.first_seq, ticket, role }) !==
|
|
243
|
+
null) ||
|
|
244
|
+
failedCheaper({ state, ticket, role })
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The model a fresh agent launches on, from the run's config and the
|
|
249
|
+
* answer the launch step just journaled. A redo launches on the model its
|
|
250
|
+
* first try journaled.
|
|
251
|
+
*/
|
|
252
|
+
export const launchPick = ({
|
|
253
|
+
records,
|
|
254
|
+
state,
|
|
255
|
+
config,
|
|
256
|
+
ticket,
|
|
257
|
+
role,
|
|
258
|
+
step,
|
|
259
|
+
}: {
|
|
260
|
+
records: JournalRecord[]
|
|
261
|
+
state: RunState
|
|
262
|
+
config: EngineConfig
|
|
263
|
+
ticket: number | null
|
|
264
|
+
role: AgentRole
|
|
265
|
+
step: LaunchTry
|
|
266
|
+
}): ModelPick =>
|
|
267
|
+
(step.redo
|
|
268
|
+
? redonePick({ records, first_seq: step.first_seq, ticket, role })
|
|
269
|
+
: null) ??
|
|
270
|
+
pickModel({
|
|
271
|
+
role,
|
|
272
|
+
models: modelsOf({ config }),
|
|
273
|
+
failed_cheaper: failedCheaper({ state, ticket, role }),
|
|
274
|
+
answer: modelAnswerOf({ records, ticket, role }),
|
|
275
|
+
})
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { ENTRY_FIELDS } from './entry-fields'
|
|
4
|
+
|
|
5
|
+
import { AgentSessionSchema } from '../agents/agent-launcher'
|
|
6
|
+
import {
|
|
7
|
+
AgentRoleSchema,
|
|
8
|
+
FileModeSchema,
|
|
9
|
+
RoleResultSchema,
|
|
10
|
+
} from '../agents/role-results'
|
|
11
|
+
import { GateCheckSchema } from '../gates/gate-schemas'
|
|
12
|
+
import { ModelReasonSchema } from '../jev/model-pick'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The journal entries of agent turns: started, finished or failed, their
|
|
16
|
+
* sessions, and shared `.git` changes during a turn.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An agent turn was started, with its prompt word for word. A follow-up in a
|
|
21
|
+
* fix loop names the session it went to in `follow_up_of`, and `prompt` holds
|
|
22
|
+
* the follow-up message. `model` is the short name it launched on (a
|
|
23
|
+
* follow-up's is its session's); `null` in journals from before #600. The
|
|
24
|
+
* exact version is in the turn's `agent_session`. `model_reason` is why it
|
|
25
|
+
* launched on that model (#603; a follow-up's is its session's); `null` in
|
|
26
|
+
* journals from before it.
|
|
27
|
+
*/
|
|
28
|
+
export const AgentStartedEntrySchema = z.object({
|
|
29
|
+
...ENTRY_FIELDS,
|
|
30
|
+
kind: z.literal('agent_started'),
|
|
31
|
+
content: z.object({
|
|
32
|
+
role: AgentRoleSchema,
|
|
33
|
+
prompt: z.string(),
|
|
34
|
+
follow_up_of: z.string().nullable().default(null),
|
|
35
|
+
model: z.string().nullable().default(null),
|
|
36
|
+
model_reason: ModelReasonSchema.nullable().default(null),
|
|
37
|
+
}),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/** The session an agent turn ran in; `null` in journals from before #363. */
|
|
41
|
+
const SESSION_FIELDS = { session_id: z.string().nullable().default(null) }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* An agent finished with a result that fits its role's schema. One option
|
|
45
|
+
* per role: keep it in step with `RoleResultSchema` (a test checks every
|
|
46
|
+
* role parses).
|
|
47
|
+
*/
|
|
48
|
+
export const AgentFinishedEntrySchema = z.object({
|
|
49
|
+
...ENTRY_FIELDS,
|
|
50
|
+
kind: z.literal('agent_finished'),
|
|
51
|
+
content: z.discriminatedUnion('role', [
|
|
52
|
+
RoleResultSchema.options[0].extend(SESSION_FIELDS),
|
|
53
|
+
RoleResultSchema.options[1].extend(SESSION_FIELDS),
|
|
54
|
+
RoleResultSchema.options[2].extend(SESSION_FIELDS),
|
|
55
|
+
RoleResultSchema.options[3].extend(SESSION_FIELDS),
|
|
56
|
+
RoleResultSchema.options[4].extend(SESSION_FIELDS),
|
|
57
|
+
RoleResultSchema.options[5].extend(SESSION_FIELDS),
|
|
58
|
+
RoleResultSchema.options[6].extend(SESSION_FIELDS),
|
|
59
|
+
RoleResultSchema.options[7].extend(SESSION_FIELDS),
|
|
60
|
+
RoleResultSchema.options[8].extend(SESSION_FIELDS),
|
|
61
|
+
]),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* How an agent's turn failed:
|
|
66
|
+
* - `agent`: the turn itself failed (an error result, a timeout).
|
|
67
|
+
* - `result`: no structured output, or output that misfits the role's schema.
|
|
68
|
+
* - `guard`: the after-turn check found a change the role may not make.
|
|
69
|
+
* - `engine`: the engine's side failed (the SDK crashed, or a follow-up's
|
|
70
|
+
* session is gone).
|
|
71
|
+
* - `evidence`: a test-writer's `already_done` evidence did not check out
|
|
72
|
+
* (#495). Replay makes it from a failed `already_done_checked`, not from
|
|
73
|
+
* an `agent_failed` record.
|
|
74
|
+
* - `chore`: a chore the writer's result asked the engine for could not be
|
|
75
|
+
* done (#618): a file mode on a path the role may not write, an unknown
|
|
76
|
+
* generator, or a failed one. See `chores_done`.
|
|
77
|
+
*
|
|
78
|
+
* The decision step counts all but `engine` as failed tries; an engine
|
|
79
|
+
* failure starts a fresh agent without using one.
|
|
80
|
+
*/
|
|
81
|
+
export const AgentFailureSchema = z.enum([
|
|
82
|
+
'agent',
|
|
83
|
+
'result',
|
|
84
|
+
'guard',
|
|
85
|
+
'engine',
|
|
86
|
+
'evidence',
|
|
87
|
+
'chore',
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
export type AgentFailure = z.infer<typeof AgentFailureSchema>
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* An agent's turn failed, journaled once per turn with how it failed.
|
|
94
|
+
* `session_gone`: a follow-up found its session closed or gone (#521), so no
|
|
95
|
+
* agent ran. Replay forgets the session as a closed one, and counts no engine
|
|
96
|
+
* failure; the fix round goes to a fresh agent. `false` in older journals.
|
|
97
|
+
* `out_of_turns`: the turn reached its role's turn limit (#543), as the
|
|
98
|
+
* launcher said; the PR lists such tickets. `false` in older journals.
|
|
99
|
+
*/
|
|
100
|
+
export const AgentFailedEntrySchema = z.object({
|
|
101
|
+
...ENTRY_FIELDS,
|
|
102
|
+
kind: z.literal('agent_failed'),
|
|
103
|
+
content: z.object({
|
|
104
|
+
role: AgentRoleSchema,
|
|
105
|
+
error: z.string(),
|
|
106
|
+
failure: AgentFailureSchema.default('agent'),
|
|
107
|
+
...SESSION_FIELDS,
|
|
108
|
+
session_gone: z.boolean().default(false),
|
|
109
|
+
out_of_turns: z.boolean().default(false),
|
|
110
|
+
}),
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The engine did the chores a writer's result asked for (#618), after its
|
|
115
|
+
* turn and before the checks: `file_modes` it set, and `checks`, one
|
|
116
|
+
* `generate` check per named generator it ran. `problems` lists what it
|
|
117
|
+
* could not do (a path the role may not write, an unknown generator, a
|
|
118
|
+
* failed one); with any, an `agent_failed` (`chore`) follows, for the
|
|
119
|
+
* role's fix loop.
|
|
120
|
+
*/
|
|
121
|
+
export const ChoresDoneEntrySchema = z.object({
|
|
122
|
+
...ENTRY_FIELDS,
|
|
123
|
+
kind: z.literal('chores_done'),
|
|
124
|
+
content: z.object({
|
|
125
|
+
role: AgentRoleSchema,
|
|
126
|
+
file_modes: z.array(FileModeSchema),
|
|
127
|
+
checks: z.array(GateCheckSchema),
|
|
128
|
+
problems: z.array(z.string()),
|
|
129
|
+
}),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/** The launcher's summary of one agent turn's model session. */
|
|
133
|
+
export const AgentSessionEntrySchema = z.object({
|
|
134
|
+
...ENTRY_FIELDS,
|
|
135
|
+
kind: z.literal('agent_session'),
|
|
136
|
+
content: z.object({ role: AgentRoleSchema, session: AgentSessionSchema }),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The engine closed an agent's session: nothing can send it a follow-up
|
|
141
|
+
* anymore (its step's result was accepted, or its step ended another way).
|
|
142
|
+
* The decision step never follows up a closed session, even one a later
|
|
143
|
+
* record names again; a fix round that would have gone to it goes to a
|
|
144
|
+
* fresh agent.
|
|
145
|
+
*/
|
|
146
|
+
export const AgentSessionClosedEntrySchema = z.object({
|
|
147
|
+
...ENTRY_FIELDS,
|
|
148
|
+
kind: z.literal('agent_session_closed'),
|
|
149
|
+
content: z.object({ role: AgentRoleSchema, session_id: z.string() }),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Shared `.git` files changed during an agent's turn by something the
|
|
154
|
+
* engine can't attribute to the agent: config outside the ticket branch's
|
|
155
|
+
* own section, `info/exclude`, or hooks, such as another agent's
|
|
156
|
+
* `git push -u` in the same repo. The change is neither blamed on the agent
|
|
157
|
+
* nor undone; this record only notes it.
|
|
158
|
+
*/
|
|
159
|
+
export const SharedGitChangedEntrySchema = z.object({
|
|
160
|
+
...ENTRY_FIELDS,
|
|
161
|
+
kind: z.literal('shared_git_changed'),
|
|
162
|
+
content: z.object({
|
|
163
|
+
role: AgentRoleSchema,
|
|
164
|
+
/** What changed, such as `the shared .git/config changed`. */
|
|
165
|
+
changes: z.array(z.string()),
|
|
166
|
+
}),
|
|
167
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { ENTRY_FIELDS } from './entry-fields'
|
|
4
|
+
|
|
5
|
+
/** The journal entries of agent messages: each one sent, and each delivery. */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What an agent message did when it was sent:
|
|
9
|
+
* - `queued`: it waits for its recipients' next tool calls.
|
|
10
|
+
* - `not_delivered`: nobody can get it (the receiver's ticket is over, or
|
|
11
|
+
* `all` found no one); it stays in the journal.
|
|
12
|
+
* - `refused`: it broke a rule (a reviewer, a bad address, the cap).
|
|
13
|
+
*/
|
|
14
|
+
export const AgentMessageStatusSchema = z.enum([
|
|
15
|
+
'queued',
|
|
16
|
+
'not_delivered',
|
|
17
|
+
'refused',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
export type AgentMessageStatus = z.infer<typeof AgentMessageStatusSchema>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One agent message, word for word, as it was sent. Addresses look like
|
|
24
|
+
* `implementer#11`: a role and a ticket.
|
|
25
|
+
*/
|
|
26
|
+
export const AgentMessageSchema = z.object({
|
|
27
|
+
/** `msg-<n>`: n is 1 + the earlier `agent_message` records in the journal. */
|
|
28
|
+
id: z.string().min(1),
|
|
29
|
+
/** The sender's address. */
|
|
30
|
+
from: z.string(),
|
|
31
|
+
/** What the sender asked for: an address, or `all`. */
|
|
32
|
+
to: z.string(),
|
|
33
|
+
text: z.string(),
|
|
34
|
+
status: AgentMessageStatusSchema,
|
|
35
|
+
/** The addresses it waits for; empty unless queued. */
|
|
36
|
+
recipients: z.array(z.string()),
|
|
37
|
+
/** Why it was refused or not delivered; `null` when queued. */
|
|
38
|
+
reason: z.string().nullable(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export type AgentMessage = z.infer<typeof AgentMessageSchema>
|
|
42
|
+
|
|
43
|
+
/** An agent sent a message. `ticket` and `role` are the sender's. */
|
|
44
|
+
export const AgentMessageEntrySchema = z.object({
|
|
45
|
+
...ENTRY_FIELDS,
|
|
46
|
+
kind: z.literal('agent_message'),
|
|
47
|
+
content: AgentMessageSchema,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/** Messages handed to their receiver at one tool call, word for word. */
|
|
51
|
+
export const AgentMessageDeliveredSchema = z.object({
|
|
52
|
+
/** The receiver's address. */
|
|
53
|
+
to: z.string(),
|
|
54
|
+
/** The ids of the messages handed over together. */
|
|
55
|
+
ids: z.array(z.string()),
|
|
56
|
+
/** The tool call they rode on. */
|
|
57
|
+
tool_name: z.string().nullable(),
|
|
58
|
+
/** The additional context the agent got, word for word. */
|
|
59
|
+
text: z.string(),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export type AgentMessageDelivered = z.infer<typeof AgentMessageDeliveredSchema>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Agent messages reached their receiver. `ticket` and `role` are the
|
|
66
|
+
* receiver's; the record's time is when it saw them.
|
|
67
|
+
*/
|
|
68
|
+
export const AgentMessageDeliveredEntrySchema = z.object({
|
|
69
|
+
...ENTRY_FIELDS,
|
|
70
|
+
kind: z.literal('agent_message_delivered'),
|
|
71
|
+
content: AgentMessageDeliveredSchema,
|
|
72
|
+
})
|