Repository navigation
Expand file tree
/
Copy pathsettings.go
More file actions
5122 lines (4786 loc) · 233 KB
/
Copy pathsettings.go
File metadata and controls
5122 lines (4786 loc) · 233 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package config
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/Agent-Field/codeaf/internal/crewroute"
"github.com/Agent-Field/codeaf/internal/ctxbudget"
"github.com/Agent-Field/codeaf/internal/env"
"github.com/Agent-Field/codeaf/internal/money"
"github.com/Agent-Field/codeaf/internal/pool/poolcfg"
"github.com/Agent-Field/codeaf/internal/provider"
"github.com/Agent-Field/codeaf/internal/roles"
"github.com/Agent-Field/codeaf/internal/search"
"github.com/Agent-Field/codeaf/internal/store"
"github.com/Agent-Field/codeaf/internal/taxonomy"
"github.com/Agent-Field/codeaf/internal/telemetry"
)
// The settings registry is the one place a user-tunable knob is written down.
// Every row declares how it reads today, how it is applied, where it persists,
// and which environment variable pins it — so a new knob is a row here rather
// than a new lookup scattered through the tree, and the completeness test in
// settings_test.go fails the build until it is registered.
//
// Resolution is the budget rail's order, generalized: environment → the
// profile's config.json → the built-in default. The environment always wins,
// and a pinned row renders read-only rather than letting the surface fight the
// shell it was launched from.
// SettingKind decides how a row reads, edits, and validates.
type SettingKind int
const (
// SettingModel opens the existing capability-filtered model picker.
SettingModel SettingKind = iota
SettingDollars
SettingDuration
SettingPercent
SettingCount
SettingBool
SettingChoice
SettingText
)
// Category names are the faint lowercase words the sheet may announce a section
// with (15). There were seven, and five of them were labels doing structure's
// job: `rhythm`, `documents & vision` and `sharing` each announced two rows or
// one, which is a header naming a mechanism rather than a section a reader could
// otherwise not place. Deleting them is 15's own test — the rows still read,
// because position and spacing already said what the word said.
//
// SPENDING IS MONEY AND NOTHING ELSE, and that is why there are six words here
// rather than four. `spending` had grown to hold twenty rows answering four
// different questions — what may it spend, what may it run without asking, how
// does it run tasks, and which workers exist — so a person looking for "how much
// may it spend" read about load averages and repair rounds first. The three
// questions are three sections now, and the implementation is the
// argument: a category is what a row is ABOUT, and a category that answers four
// questions is a drawer rather than a section.
const (
// CategoryModels is what runs the work: one row per role the router has,
// then the capability models beside them.
CategoryModels = "models"
// CategorySpending is every dollar the product will spend without asking,
// and NOTHING that is not a dollar.
CategorySpending = "spending"
// CategorySafety is what codeaf may do without asking you first: the
// approval gate, its exceptions, the model that stands in for you, and the
// two clocks that answer when nobody does.
CategorySafety = "safety"
// CategoryTasks is how work you can walk away from is run — how it starts,
// how it is checked, how much of it happens at once, and on whose hands.
CategoryTasks = "tasks"
// CategoryTeams is what every team inherits when it says nothing of its
// own: who answers a member's question, what a team may spend in a day,
// and how deep teams may nest (teamdefaults.go).
CategoryTeams = "teams"
// CategoryPractice is what codeaf does with its own time, and what it
// remembers of yours.
CategoryPractice = "memory & practice"
// CategoryInterface is how the surface draws itself, and how it signs the
// work that leaves the machine.
CategoryInterface = "interface"
)
// The old spellings, kept as aliases so a surface that still names one keeps
// compiling while it is being ported. They are the same four words; nothing
// resolves to a group that no longer exists.
const (
CategoryMoney = CategorySpending
CategoryLearning = CategoryPractice
CategoryAppearance = CategoryInterface
)
// SettingCategories is the render order of the sheet. Spending leads the three
// new sections because "what may it spend" is the question people arrive with;
// safety and tasks follow it in the order the design's own hierarchy names.
var SettingCategories = []string{
CategoryModels, CategorySpending, CategorySafety, CategoryTasks,
CategoryTeams, CategoryPractice, CategoryInterface,
}
// Persisted keys are also the json field names in the profile's config.json.
// KeyDailyBudget keeps the name /budget default already writes.
const (
KeyDailyBudget = "daily_budget_usd"
KeyPlanConsent = "plan_consent_usd"
KeyDocumentEngine = "document_engine"
KeyVisionModel = "vision_model"
// KeyModelPool is the stored word the pool's resolver takes: the same three
// answers the CODEAF_MODEL_POOL pin may give it
// (internal/pool/poolcfg). The resolver beside the search rows is the one
// place the process environment is read for the pool.
KeyModelPool = "model_pool"
// KeyModelPoolPublicKey is the trusted key a fetched pool index is checked
// under: base64 text beside the pool row it narrows, and empty for the
// key the binary carries. The environment pin CODEAF_MODEL_POOL_PUBLIC_KEY
// outranks it, through the same resolver.
KeyModelPoolPublicKey = "models.pool.public_key"
KeyAttribution = "attribution"
KeyAttributionModel = "attribution.model"
KeySplitPct = "split_pct"
// The two rows the v3 chat surface keeps on disk BESIDE the conversation:
// what was typed, and what was half-typed. They are one pair of questions —
// "may codeaf remember my own words between sessions" — and they are two
// rows rather than one because they answer it at different depths: history
// is every prompt ever submitted from this machine, the draft is the single
// unsent sentence in front of you right now, and a person who wants the
// second without the first (or the reverse) is not confused.
KeyHistoryEnabled = "history.enabled"
KeyDraftPersist = "draft.persist"
// KeyTelemetry is the anonymous-usage switch: one row, default on, off
// turns the whole pipe (see internal/telemetry). It sits beside the
// history row because they answer the same question at different
// depths — "may codeaf record what happened on this machine" — and
// the session counters count either way: only the sending asks.
KeyTelemetry = "telemetry"
// The v3 session's own keys. They are DOTTED where the older ones are
// snake_case because they name a path into a settings tree the file writer
// will eventually hold — tools.approval is a map, models.roles is a map —
// and the flat text rows below are the readable stand-in until it lands.
KeyToolApprovalMode = "tools.approvalMode"
KeyToolApprovals = "tools.approval"
// KeyBashApprovals is the ordered rule list for the one tool whose arguments
// are a language. The row above answers per TOOL — "never ask me about read"
// — and there is no useful per-tool answer for bash: a person who allowed the
// tool would have allowed every command it will ever be handed. This row is
// where the answer can name the command, which is what internal/approval's
// bash patterns are for and where the consent card's "always, this command"
// lands (approvalmemory.go).
KeyBashApprovals = "tools.bashPatterns"
// KeyGuardian turns on the small model that answers a tool prompt before you
// are asked (internal/session's guardian.go). It is named under `approval.`
// rather than beside the two `tools.` rows above because it is not a rule
// about tools at all: it is a statement about WHO ANSWERS — the person, or a
// model standing in for them — and grouping it with the rule rows would file
// it as one more exception in a list of exceptions.
KeyGuardian = "approval.guardian"
// KeyConsentTimeout is how long an approval question counts down before it
// PAUSES and keeps waiting. It never answers for the person — silence is
// not a no (F41) — and it stops the moment a key is pressed, because a
// person who has started reading is a person who is going to answer.
//
// Seconds, not a duration string, for the reason [KeyTaskAutoApprove] is
// spelled that way: the number is small and read at a glance off a line that
// is counting it down. 0 turns the clock off and the question waits from
// the start, which is what a person who reads every prompt wants.
KeyConsentTimeout = "approval.timeout_seconds"
// KeyPromptProfile is how much codeaf puts in front of the model before a
// person has typed: the whole page and the whole tool list, or the lean
// pair a small window can afford (internal/session's promptprofile.go).
//
// IT IS A ROW BECAUSE THE DERIVATION CAN BE WRONG. The profile is settled
// from the model's context window and that is right almost every time, but
// an endpoint that reports a window its loaded model does not really have
// leaves the person with no way to say so, and a derived state with no row
// anywhere is a state nobody can read off a screen. `auto` is the default
// and keeps the derivation; the other two words are the person overruling
// it, in the same words [EnvPromptProfile] takes.
KeyPromptProfile = "prompt.profile"
KeyKnowledgeModel = "knowledge.model"
KeyTierLowModel = "models.tiers.low"
KeyTierHighModel = "models.tiers.high"
// KeyTierWorkerModel is the seat that does the work — the worker of every
// task, the parts it hands out, the nodes of an adaptive run
// ([roles.TierWorker]). It reads from the PROFILE ALONE, unlike the two rows
// above it, for the reason the task model row does: a repository that could
// answer this could send a visitor's work — and their credit — to a model
// they never picked, by being cloned.
KeyTierWorkerModel = "models.tiers.worker"
// KeyTierReflexModel is the third tier, and the only one with a model in it
// out of the box. It is read TWICE A TURN by the routing and extraction
// calls the reflex tier exists for (internal/reflex), which is a rhythm no
// other auxiliary call has: a model that costs a tenth of a cent a call is
// free on the low tier and is real money here. So the row ships pointed at
// a model that costs near nothing rather than at "follows the conversation"
// — a person who never opens the sheet gets the cheap thing, and a person
// who clears the row gets the conversation's own model, deliberately.
KeyTierReflexModel = "models.tiers.reflex"
// KeyTierMastermindModel is the fourth tier and the only one whose model is
// chosen for THINKING rather than for a price. Two roles ride it — the
// planner that amends an adaptive run's plan after every node, and the
// designer that writes a harness page everybody afterwards runs — and both
// were on the careful-work tier beside the check on finished work, which made
// one figure answer two unrelated bills: the careful calls are many and short,
// these are few and decide what all the other calls do.
//
// It is the one row whose value may carry a LEVEL as well as a model
// (`moonshotai/kimi-k3:low`), because it is the one row where how hard the
// model thinks is the point. Every tier row accepts the notation —
// [ValidateTierValue] is the same gate on all five — but this is the one the
// shipped crew writes it into.
KeyTierMastermindModel = "models.tiers.mastermind"
// KeyMouse is whether the surface reports the mouse at all. ON is the
// default ([DefaultMouse]), because hover, click and the wheel are v3's own
// language and the thing they cost is bought back by a key: an alt-screen
// app that reports the mouse OWNS every drag, so the terminal's native text
// selection dies the moment reporting starts — and `ctrl+s` hands the
// pointer back for as long as somebody is dragging with it.
//
// THIS COMMENT SAID "OFF IS THE DEFAULT" while [DefaultMouse] said on, and a
// stale sentence here is the expensive kind: it is the first thing anybody
// reads when the answer to "does codeaf ask for the mouse at all" decides
// whether a pointer bug is in this program or in the terminal. One source of
// truth — [MouseModes] states the choice and [DefaultMouse] states the
// answer, and this row's doc may not contradict either.
KeyMouse = "ui.mouse"
// KeyTimestamps is how much of the clock the v3 conversation carries: a
// footer under every finished turn, only the coarse marks where the
// conversation was put down and picked up again, or nothing at all
// (internal/tui3's render.go). It is named beside the mouse row because it
// is the same kind of question — how much furniture the transcript draws —
// and it is a CHOICE rather than a bool because "less" and "none" are two
// different answers a reader gives for two different reasons.
KeyTimestamps = "ui.timestamps"
// KeyTaskColumn is whether the v3 chat opens with the task roster's column
// standing beside the conversation. It is a BOOLEAN — the column has no
// middle rung: its two narrower tiers are decided by the frame's own width,
// and the one answer a person gives it by hand is whether the column is
// there at all.
//
// A person who put v3's column away has said
// nothing whatever about v2's three rungs.
KeyTaskColumn = "ui.task_column"
// KeyQuickSwitch is whether the conversation switcher (internal/tui3's
// hop.go) SWITCHES ON EACH PRESS of `ctrl+tab` — that chord lands you in the
// previous conversation at once and pressing again keeps going — or opens as
// a card that waits for `enter`. It is a BOOLEAN because the two behaviours
// are the whole of the choice: there is no third rung between "the key is
// the switch" and "the key is the menu".
//
// IT IS `ctrl+tab` AND NOT THE BINDING, which this doc said for a long while
// and which the code has never done ([app.hopKey] reads the setting only for
// the alias and its reverse). The binding always browses: it is the chord
// everybody has, so it is the one that has to behave the same on every
// machine, and a card that waits for `enter` is the behaviour that needs
// nothing from the terminal. Neither gesture watches for a modifier being
// RELEASED — ordinary terminals do not report that at all (hop.go).
KeyQuickSwitch = "ui.quick_switch"
// KeyHints is whether the v3 chat shows its earned hints — the one-line tips
// in the slot above the message box that each retire once the key or command
// they name has been used (internal/tui3's notice.go) — and, with them, the
// one-line what's-new notes a new build may say. It is one row and not two
// because a person who has silenced the tips has said they know the surface,
// and being told about features is the same conversation.
KeyHints = "ui.hints"
// KeyUpdateAuto is whether codeaf INSTALLS a new release on its own. ON is
// the default: a released codeaf installs a newer stable release as it opens
// and starts on it (cmd/codeaf's prelaunch.go), and the chat's countdown
// installs in the background when that could not finish. Only a stable build
// named codeaf is ever moved this way; dev, staging, rc and source builds are
// told, never replaced ([codeupdate.AutomaticFor]). A person who does not want
// it has a row, an environment variable and one chord on the offer itself.
// OFF means nothing is installed until the
// person asks — `/update` still installs a release on request, because a
// person who typed it has said what they want — but the LAUNCH CHECK STILL
// RUNS and still names the release on one dim line, because knowing is not
// acting (cmd/codeaf's surface wires the check separately from this row).
// CODEAF_NO_UPDATE_CHECK=1 is the switch that makes no request at all. It is a
// boolean because there is no third answer between "keep me current" and
// "leave me alone until I ask".
KeyUpdateAuto = "update.auto"
// KeyWork controls whether completed turn machinery starts folded or open.
KeyWork = "ui.work"
// KeyIcons selects the step icon repertoire independently of colour.
KeyIcons = "ui.icons"
// KeyTeamsShare stays local because every computer chooses whether its teams travel.
KeyTeamsShare = "teams.share"
// KeyTaskAudit is whether an independent auditor verifies each task node
// before its work may merge (internal/session's task_audit.go). It sits
// beside the guardian because both spend a model on the person's behalf:
// the guardian to answer, the auditor to check.
KeyTaskAudit = "task.audit"
// KeyReplyGuard is whether a reply that has stopped being language is cut
// and asked again (internal/provider's streamguard.go). It is a row because
// the cut is a JUDGEMENT about somebody else's text, and a person who writes
// in four alphabets, or who asks for pages of repeated output outside a code
// fence, is entitled to say they would rather see whatever arrives.
//
// The silence watchdog beside it has no row: a request that produced nothing
// at all has failed by any reading.
KeyReplyGuard = "reply.guard"
// KeyTaskStart is what a bare `/task <brief>` COSTS TO SHAPE, and it is no
// longer a question about shape at all (internal/tui3's taskcommand.go).
// Every answer starts ONE WORKER. What the answers differ in is whether a
// small sizing call reads the brief first, because a yes from that call is
// what arms the worker to hand the work out mid-run once it has opened the
// material and found the width is real (internal/session's task_divide.go).
//
// It is a row because the question is not really about one task. Somebody who
// does not want the sizing call on their bill is refusing it every time, and
// that is a preference stated once rather than on every `/task`.
//
// THE PLANNED-GRAPH ANSWER IS GONE, and with it the last way a preference
// could open an adaptive run behind somebody's back. A `/task` takes one road
// now — one worker that divides itself from the material — and there is no
// second road left to prefer: no chat door reaches the planner at all, not a
// setting, not a hand on the belt, and not a form of words somebody types
// (internal/session's loop.go). A profile still holding the retired word reads
// as the default, silently, the way any word this build does not know reads
// ([TaskStartAt]).
KeyTaskStart = "task.start"
// KeyMemoryEnabled controls automatic learning from chats and jobs.
// Saved knowledge and explicit keeps remain available while it is off.
KeyMemoryEnabled = "memory.enabled"
KeyModelRoles = "models.roles"
// KeyModelFallbacks is the ordered list of models a conversation moves to
// when no endpoint serving the one it is on will accept the request at all
// (internal/provider's endpoints.go). Comma-separated slugs, first tried
// first. Empty lets the catalog pick the nearest same-class model instead.
KeyModelFallbacks = "models.fallbacks"
KeySpendRail = "session.spendRailUSD"
// KeyRouting is how a session asks the router to choose among the endpoints
// serving one model (internal/provider's velocity.go). A model is not one
// machine: the same id is fanned over several endpoints that answer at very
// different speeds for the same price, and this row is which of those
// differences the session is willing to pay attention to.
KeyRouting = "routing"
// KeyLaneGuard is whether one slow answer may be rescued by asking a second
// lane the same question while the first is still thinking (internal/lane's
// watch.go). It is a separate row from [KeyRouting] because it answers a
// different question: routing says WHICH lane a request prefers, and this
// says whether a request that has already gone wrong is allowed to spend a
// little more to come back on time.
//
// It is on by default. The extra call is capped at one per answer and at a
// tenth of what the session spends, and it is off under price routing,
// where nobody is buying seconds at all.
KeyLaneGuard = "lane.guard"
// KeyTaskAutoApprove is the countdown a proposed task waits before it
// starts on its own (internal/session's task.go). It is named under `task.`
// rather than beside the approval rows for the reason the guardian row is
// named apart from them: this is not a rule about what may run, it is HOW
// LONG YOU GET to say something about work that is going to run either way.
//
// Seconds, not a duration string, because the number is small and read at a
// glance under a bar that is counting it down — "5" is the row, "5s" would
// be the row pretending to be a unit it never varies.
KeyTaskAutoApprove = "task.autoapprove_seconds"
// KeyTaskRepairRounds is how many times a task whose work came back
// INCOMPLETE is sent back to close the gaps before it lands as a failure
// (internal/session's task_audit.go). It is named under `task.` beside the
// countdown and the audit row because it answers their question in the third
// currency: those say how long you get to redirect work and who checks it,
// this says HOW MANY TIMES a piece of work that nearly landed is allowed to
// finish itself.
//
// A count rather than a word, and 0 turns it off: the number is the whole
// setting, and the person who wants the old behaviour — one pass, and a gap
// is a dead node — writes 0 rather than learning a vocabulary. It is small on
// purpose. Each round is another worker and another check on the same node's
// bill, and a loop that could run five times is a loop that can spend five
// times without anybody watching.
KeyTaskRepairRounds = "task.repair_rounds"
// KeyTaskParallel is how many tasks may RUN AT ONCE (internal/session's
// task_run.go). It is named under `task.` with the countdown and the repair
// count because it answers their question in the fourth currency: those say
// how long you get to redirect work, how many times it may finish itself,
// and whose hands it is in — this says HOW MUCH OF IT happens at the same
// time.
//
// BLANK IS NO LIMIT, and that is the default. The number of tasks was never
// the resource: what runs out is this machine's cores and memory — the two
// rows below — and the model provider's rate limit, which the adapter reads
// off 429s and adapts to on its own (internal/provider's limiter.go). This
// row exists for the person who wants a number anyway, and it is a QUEUE and
// not a refusal: work past the cap waits and starts when a slot frees.
KeyTaskParallel = "task.parallel"
// KeyTaskMaxLoad is the load average PER CORE at or above which no new task
// is started (internal/session's task_pressure.go). It is one of the two
// real ceilings the cap above stopped pretending to be.
//
// Per core, so that the number means the same thing on a laptop and on a
// workstation: 1.5 is "half again as many runnable threads as there are
// cores to run them", which is where the scheduler starts handing out slices
// rather than running work and one more build makes every build slower. 0
// turns the check off. It holds STARTS only — nothing already running is
// ever touched, so the pressure drains on its own.
KeyTaskMaxLoad = "task.max_load"
// KeyTaskMinFreeMB is the floor of available memory below which no new task
// is started (internal/session's task_pressure.go), in mebibytes, and the
// other half of the ceiling above.
//
// It reads the kernel's MemAvailable — what a new process could actually get
// — and not free memory, which on a working machine is near zero by design
// because the page cache has the rest. 0 turns the check off.
KeyTaskMinFreeMB = "task.min_free_mb"
// KeyCellBudget is the disk the working files of all cells may use
// together, in gibibytes (internal/cellbudget). Over it, `codeaf cell gc`
// evicts the cells opened longest ago. 0 means no limit.
KeyCellBudget = "cell.budget_gb"
// KeyTaskModel is the model a task runs on when the conversation does not
// name one for it (internal/session's taskmodel.go). It is named under
// `task.` beside the countdown rather than among the `models.` rows because
// it is not a rule about how a call is made: it is WHOSE HANDS the work that
// leaves this conversation ends up in, which is the same subject the
// countdown and the audit rows are about.
//
// Blank is the conversation's own model, which is what every task ran on
// before the row existed: a node is the same worker doing the same job
// somewhere quieter.
KeyTaskModel = "task.model"
// KeyTaskRunsOn remembers a device rather than its name, so a rename does
// not move the person's default. Blank keeps tasks on this computer.
KeyTaskRunsOn = "task.runs_on"
// TaskRunsHere is the setting's empty value in the person's words.
TaskRunsHere = "this computer"
// KeyTaskSettle is WHO DECIDES a task that finished but that nobody could
// check (internal/session's task_contract.go). It sits under `task.` beside
// the audit row because it is the other end of that row's question: the audit
// says whether the work is checked at all, and this says what happens when
// the check came back with nothing.
//
// `ask` is the default and puts the decision on the landed card, where the
// person answers it with one press. `auto` hands it to the chat: the landing
// note tells the model to read the work and settle it, and to come back only
// when it genuinely cannot tell. Both leave the same three answers available
// to both of them — this row changes who is asked first, and nothing else.
KeyTaskSettle = "task.settle"
// The web-search rows. They are four rather than one because they answer
// four separable questions: WHERE a lookup goes, and the three credentials
// that change what "where" can mean. A person with no key still searches —
// internal/search's last rung takes none — so the keys are an upgrade and
// never a prerequisite, and none of the three has to be answered for the
// session to be able to look something up.
KeySearchProvider = "search.provider"
KeyExaKey = "search.exaKey"
KeyFirecrawlKey = "search.firecrawlKey"
KeyJinaKey = "search.jinaKey"
// The two rows that let a person connect their Google account
// (internal/connect). They are a PAIR and neither is useful alone: an
// application id names the application asking, and its secret proves the
// ask came from it, so a build holding one of them can offer exactly
// nothing. The session reads them together for that reason
// ([GoogleOAuthClientAt]), and offers the connect tools only when both are
// answered.
//
// Answering them REPLACES the registration this build ships with
// (connect_defaults.go): a person or a deployment that wants its own
// application asking — its own quota, its own consent screen, its own
// revocation — writes it here or exports it, and nothing else changes.
KeyGoogleOAuthClient = "google_oauth_client"
KeyGoogleOAuthSecret = "google_oauth_secret"
// The one row that lets a person replace the Slack application this build
// ships with (connect_defaults.go). It is ONE ROW AND NOT A PAIR because
// Slack's PKCE registration has no secret. An organisation that registers
// its own internal application to escape the outside-Marketplace throttle
// pastes that application's id here.
KeySlackOAuthClient = "slack_oauth_client"
// The four context-law knobs. Fill is how much of a model's window any
// agent may use before compaction fires — and WHETHER A PERSON SET IT is
// itself part of the law, because a conversation nobody has pinned folds
// against a line derived from its model's window instead
// (ctxbudget.PinnedFillPercent, internal/session's compactThresholdOf);
// the reserve is the room every
// call keeps for its answer and its reasoning; the working set caps what
// an agent keeps quoted in front of itself however large the window is;
// and reuse caps how many times over it may re-send that working set.
KeyContextFill = "context_fill_pct"
KeyCompletionReserve = "completion_reserve"
KeyWorkingSet = "working_set_tokens"
KeyContextReuse = "context_reuse_pct"
)
// ToolApprovalModes are the three answers the tool gate can be set to, in the
// order they widen: ask about everything, run everything, refuse everything.
// They are internal/approval's own words — the registry must not invent a
// fourth spelling for a decision that package already names.
var ToolApprovalModes = []string{"prompt", "allow", "deny"}
// The guardian row's two answers. It is a CHOICE and not a bool for the reason
// every other two-word row here is one: "off/on" is what the sheet renders and
// what the file holds, and a person reading their config.json back should find a
// word they chose rather than a `true` they have to remember the question for.
const (
GuardianOff = "off"
GuardianOn = "on"
)
// GuardianModes lists them, off first — which is also the default, and the order
// the row widens in.
var GuardianModes = []string{GuardianOff, GuardianOn}
const (
MouseOff = "off"
MouseOn = "on"
)
// MouseModes lists them, on first — which is also the default: hover, click
// and the wheel are the surface's own language. Selecting text does not die for
// it — ctrl+s hands the pointer to the terminal for as long as somebody is
// dragging with it, and ctrl+b copies from the keyboard — but a person who
// wants the terminal's plain drag at all times turns the row off.
//
// Shift+drag is the answer this hint used to give, and it was the wrong one:
// it is true, it is a fact about terminals rather than about this product, and
// the terminals that have it disagree about the modifier (Option in iTerm2).
// A key this surface owns is a key this surface can print.
var MouseModes = []string{MouseOn, MouseOff}
// DefaultMouse is on.
const DefaultMouse = MouseOn
const (
TaskAuditOff = "off"
TaskAuditOn = "on"
)
// TaskAuditModes lists them, on first — which is also the default: a node
// that verifies its own work is the whole point of the verified frontier,
// and the audit's cost is the price of trusting what merges.
var TaskAuditModes = []string{TaskAuditOn, TaskAuditOff}
// DefaultTaskAudit is on.
const DefaultTaskAudit = TaskAuditOn
const (
ReplyGuardOff = "off"
ReplyGuardOn = "on"
)
// ReplyGuardModes lists them, on first — which is also the default. A reply that
// has come apart is worth almost nothing and costs the whole of the next
// request, because it goes back into the conversation and the model reads its
// own soup before writing more.
var ReplyGuardModes = []string{ReplyGuardOn, ReplyGuardOff}
// DefaultReplyGuard is on.
const DefaultReplyGuard = ReplyGuardOn
// The two answers to [KeyTaskSettle]: who settles a task that finished with
// nobody able to say whether it holds.
const (
// TaskSettleAsk puts it in front of the person, on the landed card.
TaskSettleAsk = "ask"
// TaskSettleAuto hands it to the chat, which reads the work and decides.
TaskSettleAuto = "auto"
)
// TaskSettleModes lists them, ask first — which is also the default. Deciding
// on somebody's behalf is a thing they say yes to, never a thing they get by
// saying nothing.
var TaskSettleModes = []string{TaskSettleAsk, TaskSettleAuto}
// DefaultTaskSettle is ask.
const DefaultTaskSettle = TaskSettleAsk
// The two answers to [KeyTaskStart], and they are not two settings but one
// question asked once instead of on every `/task`: what a bare `/task` pays to
// find out before its one worker starts.
//
// sized one worker, with the sizing call read over the brief first. A yes
// from it arms that worker to hand the work out as it goes, once it
// has opened the material and found the width is real — so wide work
// runs wide without a planner ever being asked to guess at it.
// single one worker, and the sizing call is not made at all. The work can
// still divide itself, but only off what its own brief already says
// (internal/splitgate), because nothing was read over it.
//
// THERE WERE FOUR, AND BOTH THE RETIRED ONES NAMED A CARD OR A ROAD THAT IS
// GONE. `ask` went with the two-row chooser a yes from the sizing call used to
// raise; `adaptive` went with the planned-graph road itself, which a chat turn
// may no longer open at all. Neither retirement is allowed to be felt: a profile
// still holding either word reads as the default, silently, because [TaskStartAt]
// treats a word this build does not know as no answer at all — and being told
// that a preference set months ago is now an error is the one thing a retirement
// must never do.
// NEITHER RETIRED WORD IS SPELLED HERE, and `ask` set that precedent: a constant
// for a word nothing may write, nothing may offer and nothing may resolve to is a
// name the next reader has to be told is not a mode. The retirement needs no
// constant to work — it is [TaskStartModes] not containing the word, which is
// what the sheet, [writeChoice] and [TaskStartAt] all read.
const (
TaskStartSized = "sized"
TaskStartSingle = "single"
)
// TaskStartModes lists what may be chosen, the default first. The retired word
// is not in it, and that absence is the whole mechanism — the sheet's choices,
// the writer's validation and the resolver all read this one list.
var TaskStartModes = []string{TaskStartSized, TaskStartSingle}
// DefaultTaskStart is sized: one worker that knows whether it is allowed to
// divide, which is the shape the measured road is built around.
const DefaultTaskStart = TaskStartSized
// The timestamps row's three answers, and they are a LADDER rather than three
// unrelated pictures: each rung draws strictly less of the clock than the one
// above it.
//
// footers the turn footer (· 14:02 · 2m12s · 3 tools · $0.04 ·) AND the
// gap and day marks, which are what a footer is read against
// separators only the marks: where the conversation was put down for ten
// minutes, and where a day ended
// off no clock at all
//
// The middle rung exists because the two features answer the same question at
// different costs: a reader who wants to know that yesterday's exchange was
// yesterday does not necessarily want a line of figures under every turn, and a
// footer with no day mark above it would be a time with no date.
const (
TimestampsFooters = "footers"
TimestampsSeparators = "separators"
TimestampsOff = "off"
)
// TimestampModes lists them richest first, which is also the default order the
// row widens in.
var TimestampModes = []string{TimestampsFooters, TimestampsSeparators, TimestampsOff}
const (
WorkFold = "fold"
WorkOpen = "open"
)
var WorkModes = []string{WorkFold, WorkOpen}
// IconModes keeps the normal rich presentation and its compatibility floor
// selectable without changing the terminal's colour or animation settings.
const (
IconsAuto = "auto"
IconsRich = "rich"
IconsPlain = "plain"
)
var IconModes = []string{IconsAuto, IconsRich, IconsPlain}
const DefaultWork = WorkFold
// DefaultTimestamps is the footers. When a turn took two minutes and cost four
// cents, those are facts about work the person paid for, and a transcript that
// never says when anything happened cannot be read back a day later.
const DefaultTimestamps = TimestampsFooters
const (
MemoryOff = "off"
MemoryOn = "on"
)
// MemoryModes lists the default first; saved choices remain authoritative.
var MemoryModes = []string{MemoryOn, MemoryOff}
const DefaultMemory = MemoryOn
// MemoryCostNote is the cost disclosure shared by settings surfaces.
const MemoryCostNote = "Learns from chats and jobs so later work can use what you learned. On by default; uses extra model calls on your key. See actual costs in Spending."
// The prompt profile's three answers, and they are three rather than two
// because the honest default is not a size at all: it is "work it out". A row
// offering only `lean` and `full` would force everybody to hold an opinion
// about a figure the catalog already knows.
const (
// PromptProfileAuto derives the profile from the model's context window,
// which is what every session did before this row existed.
PromptProfileAuto = "auto"
// PromptProfileLean is the shorter page and the shorter tool list, whatever
// window the model reports.
PromptProfileLean = "lean"
// PromptProfileFull is every law on the page and every ordinary verb in the
// tool block, whatever window the model reports.
PromptProfileFull = "full"
)
// PromptProfileModes lists them, auto first, which is the default.
var PromptProfileModes = []string{PromptProfileAuto, PromptProfileLean, PromptProfileFull}
// DefaultPromptProfile is auto.
const DefaultPromptProfile = PromptProfileAuto
// EnvPromptProfile pins the profile for one launch, in the same three words the
// row takes. It is spelled here and read from here by internal/session's
// promptprofile.go, because a pin the sheet renders read-only and a pin the
// engine obeys must be one string or they drift.
const EnvPromptProfile = "CODEAF_PROMPT_PROFILE"
// The routing row's four answers. They are spelled here rather than imported
// from internal/provider for the reason [DocumentEngines] is: a settings key's
// vocabulary is a string on disk, and it must not change because a package
// renamed a constant.
const (
// RoutingLatency asks for the currently-fastest endpoint.
RoutingLatency = "latency"
// RoutingPrice asks for the cheapest one that can serve the request.
RoutingPrice = "price"
// RoutingSimple sends no preference of ours at all: when no provider is
// pinned the router's own default answers, and a pinned provider is the
// whole request.
RoutingSimple = "simple"
// RoutingOff sends no preference at all, and stops measuring with it.
RoutingOff = "off"
)
// RoutingModes lists them, simple first — which is also the default, and the
// order the row cycles in.
var RoutingModes = []string{RoutingSimple, RoutingLatency, RoutingPrice, RoutingOff}
// DefaultRouting is simple: what a person asked for is what goes on the wire,
// and nothing else does.
//
// IT WAS `latency` UNTIL THIS BUILD, and the reason it moved is that the
// choosing was not visible. Sorting by speed brings a whole apparatus with it —
// a ranking this process keeps, a price ceiling, refusals learned from earlier
// answers, a pin retired against a belief saved from an earlier run — and each
// of those is a decision nobody watched being made. What the picker showed, what
// was chosen, and what the record said were three answers to one question. Under
// this row they are one answer: with no lane pinned the request carries no
// preference at all and the router's own default routing answers it, and with a
// lane pinned that pin is the whole request. `latency` and `price` are both
// still here for somebody who wants the apparatus, one word away
// (internal/provider's velocity.go).
const DefaultRouting = RoutingSimple
// ── WHICH MACHINE, NOT WHICH MODEL ──────────────────────────────────────────
//
// A model id is an address and a LANE is one of the machines behind it. The
// same id is served by a dozen endpoints that differ by seven times on the wait
// before the first word and by twelve times on how fast they write, at roughly
// the same price — so the lane a request lands on is a bigger difference than
// most model changes, and this is where a person says something about it.
//
// The rows are per slot (`lane.talk`, `lane.work`, …) for the reason the model
// rows are: the conversation you are watching and a task worker nobody is
// waiting on want different answers, and one row for both would be a setting
// that is wrong half the time. [KeyRouting] is untouched and still means what
// it meant: it is about the whole preference, and this is about one machine.
const (
// LaneAuto lets the belief pick a lane per answer. It is the default and it
// is what an unset row reads as.
LaneAuto = "auto"
// LaneOpenRouter asks for no lane at all and lets the router balance on
// price, which is what this build did before it held an opinion.
LaneOpenRouter = "openrouter"
)
// LaneSlotTalk is the ONE slot the settings registry carries a row for: the
// conversation. Every slot has a key — a task worker can be pinned by the same
// grammar — but a panel with nine lane rows on it would be a panel about
// endpoints rather than about models, and the other eight are set from the
// picker on the model they belong to.
const LaneSlotTalk = "talk"
// LaneSettingKey is the row holding one slot's lane: `auto`, `openrouter`, or a
// lane's own name as the wire spells it.
func LaneSettingKey(slot string) string { return "lane." + slot }
// LaneBorrowKey is the row beside it: whether a PINNED lane may still be
// borrowed away from when it is slow. It is a second key rather than a fourth
// word in the first because the two are independent — borrowing means nothing
// under `auto` — and because a pin that quietly stopped being a pin the day
// somebody turned rescuing on would be a promise this build did not keep.
func LaneBorrowKey(slot string) string { return LaneSettingKey(slot) + ".borrow" }
// LaneAt is the lane one slot is held to, default [LaneAuto]. A blank or
// unreadable row reads as auto rather than as a pin nobody can see.
func LaneAt(profileDir, slot string) string {
if value, ok := persistedString(profileDir, LaneSettingKey(slot)); ok {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
return LaneAuto
}
// LaneBorrowAt is whether a pinned slot may be borrowed away from when its lane
// is slow. It is false unless somebody said so: a pin means the machine they
// named, and widening it on their behalf is not this row's to do.
func LaneBorrowAt(profileDir, slot string) bool {
value, ok := persistedBool(profileDir, LaneBorrowKey(slot))
return ok && value
}
// LanePinned is the lane named by a slot's row, and false when the row names no
// machine — which is every reading of `auto` and of `openrouter`.
func LanePinned(profileDir, slot string) (string, bool) {
value := LaneAt(profileDir, slot)
switch strings.ToLower(value) {
case LaneAuto, LaneOpenRouter, "":
return "", false
}
return value, true
}
// LanePinAt is one slot's lane row resolved into the answer the transport
// takes. The three states of the row are the three states of the pin, and a row
// nobody has written is `auto` — the belief chooses per answer.
func LanePinAt(profileDir, slot string) provider.LanePin {
if name, pinned := LanePinned(profileDir, slot); pinned {
return provider.LanePin{Lane: name, Borrow: LaneBorrowAt(profileDir, slot)}
}
if strings.EqualFold(LaneAt(profileDir, slot), LaneOpenRouter) {
return provider.LanePin{OpenRouter: true}
}
return provider.LanePin{}
}
// InstallLaneRows hands this profile's routing posture to the process-wide
// knobs the transport reads it from. It uses the RESOLVER'S entrance so loading
// a row already in force never forgets a retirement the wire earned; only a
// person's own act belongs at [provider.RepinLane].
//
// THE ROUTING ROW IS ONE OF THEM, and it is here rather than only on the
// session's own config because of the clients nobody hands one to. The harness,
// the subharness, `read_document`, `view_image` and a panel's members are all
// assembled through [Config.ClientConfig], which carries no routing answer —
// so before this line they ran on the default whatever a person had written,
// and one of them could retire a person's own pin, process-wide, before any
// wire was asked (internal/provider's velocity.go says what that cost). It is
// the CHOICE and not the resolved default, so an unwritten row installs nothing
// and every client falls to the shipped row together
// ([provider.DefaultRouting]).
func InstallLaneRows(profileDir string) {
InstallRoutingRow(profileDir)
provider.SetLanePin(LanePinAt(profileDir, LaneSlotTalk))
provider.SetLaneGuard(LaneGuardAt(profileDir))
}
// InstallRoutingRow hands the routing row ALONE to the transport, and it is the
// half of [InstallLaneRows] the settings panel calls by itself: somebody cycles
// `routing`, the row is written, and the very next request has to go out under
// it. The lane rows beside it are untouched because nothing about them changed —
// re-stating a pin here would be a resolver's write nobody asked for.
func InstallRoutingRow(profileDir string) {
provider.InstallRouting(installedRoutingFor(profileDir))
}
// installedRoutingFor is the routing row as the transport's own vocabulary, and
// the EMPTY strategy when a person has written nothing readable. The parse is
// total, so a word this build does not know installs nothing rather than taking
// a person's routing somewhere they did not ask for.
func installedRoutingFor(profileDir string) provider.RoutingStrategy {
word := RoutingChoiceAt(profileDir)
if word == "" {
return ""
}
strategy, known := provider.ParseRoutingStrategy(word)
if !known {
return ""
}
return strategy
}
// SetLane writes one slot's lane. An empty word clears the row back to auto,
// which is the same thing said two ways and both of them arrive here.
func SetLane(profileDir, slot, value string) error {
value = strings.TrimSpace(value)
if value == "" || strings.EqualFold(value, LaneAuto) {
return writeProfileValue(profileDir, LaneSettingKey(slot), LaneAuto)
}
if strings.ContainsAny(value, " \t") {
return fmt.Errorf("a lane is one name, like cloudflare")
}
return writeProfileValue(profileDir, LaneSettingKey(slot), value)
}
// SetLaneBorrow writes the borrow flag beside a pin.
func SetLaneBorrow(profileDir, slot string, borrow bool) error {
return writeProfileValue(profileDir, LaneBorrowKey(slot), borrow)
}
// DefaultLaneGuard is on: a slow answer is worth one extra call to rescue, and
// the budget around it is what keeps that true rather than a hope.
const DefaultLaneGuard = true
// LaneGuardAt resolves the speed-guard row. A malformed row reads as the
// default rather than quietly turning the rescue off — a person who never
// touched this row has not asked to wait.
func LaneGuardAt(profileDir string) bool {
if value, ok := persistedBool(profileDir, KeyLaneGuard); ok {
return value
}
return DefaultLaneGuard
}
// LaneRowWord is one slot's lane row as a person reads it: `auto`,
// `openrouter`, `pinned: cloudflare`, or `pinned: cloudflare, borrow when
// slow`. It is here rather than in a surface because the same words are what
// [WriteLaneRow] reads back, and two spellings of one row is how a row stops
// round-tripping.
func LaneRowWord(profileDir, slot string) string {
name, pinned := LanePinned(profileDir, slot)
if !pinned {
if strings.EqualFold(LaneAt(profileDir, slot), LaneOpenRouter) {
return LaneOpenRouter
}
return LaneAuto
}
if LaneBorrowAt(profileDir, slot) {
return "pinned: " + name + ", " + laneBorrowWord
}
return "pinned: " + name
}
// laneBorrowWord is the tail that says a pin may be left when it is slow. It is
// stated once because it is written on the row, read back off it, and said in
// the manual.
const laneBorrowWord = "borrow when slow"
// WriteLaneRow takes what [LaneRowWord] says and puts it back: `auto`,
// `openrouter`, a bare lane name, or either `pinned:` form.
func WriteLaneRow(profileDir, slot, raw string) error {
value := strings.TrimSpace(raw)
borrow := false
if at := strings.LastIndex(strings.ToLower(value), laneBorrowWord); at >= 0 {
borrow = true
value = strings.TrimRight(strings.TrimSpace(value[:at]), ",")
value = strings.TrimSpace(value)
}
value = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(value), "pinned:"))
if err := SetLane(profileDir, slot, value); err != nil {
return err
}
return SetLaneBorrow(profileDir, slot, borrow)
}
// The two tier names internal/roles resolves auxiliary calls under. They are
// spelled here rather than imported for the reason [DocumentEngines] is: the
// registry is a settings surface, and a settings key is a string on disk that
// must not change spelling because a package renamed a constant.
const (
ModelTierLow = "low"
ModelTierHigh = "high"
// ModelTierWorker is the seat that does the work ([roles.TierWorker]).
ModelTierWorker = "worker"
// ModelTierReflex is the per-turn tier ([roles.TierReflex]).
ModelTierReflex = "reflex"
// ModelTierMastermind is the thinking tier ([roles.TierMastermind]).
ModelTierMastermind = "mastermind"
)
// ModelTiers lists the five tier words in the order a settings surface renders
// them, cheapest first. It is [roles.Tiers] spelled as the words on disk, and
// [tierKeyFor] is total over it.
var ModelTiers = []string{ModelTierReflex, ModelTierLow, ModelTierWorker, ModelTierHigh, ModelTierMastermind}
// THE TWO ROWS THAT ARE NOT CREW SEATS arrive pointed at a model. The reflex
// and small-work tiers carry the calls a conversation makes on its own behalf —
// twice a turn for the reflex — so a person who never opened the sheet gets a
// model that costs near nothing rather than the one they are talking to. The
// three crew seats have no shipped model at all: an unpinned seat is routed per
// task (crew.go), and a build-chosen worker, planner or checker would be the
// implicit fallback the router exists to remove.
//
// Each is a bare OpenRouter id, spelled ONCE here and read by every caller
// through [TierModelAt]. Blank is still an answer on both: a row a person
// emptied on purpose reads empty and follows the model the person is talking
// to, which is [roles.Resolve]'s floor. UNSET and CLEARED are different answers
// here ([TierModelAt] says how). The low row is pinned to a DATED build on
// purpose: the bare `deepseek/deepseek-v4-flash` id resolves to the April