Repository navigation
Expand file tree
/
Copy pathtask_run.go
More file actions
9841 lines (9456 loc) · 464 KB
/
Copy pathtask_run.go
File metadata and controls
9841 lines (9456 loc) · 464 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 session
// The executor: a graph of work, a frontier, and one node's life inside a
// worktree.
//
// ── THE SHAPE IS A GRAPH, TODAY IT HOLDS ONE NODE ──
//
// [TaskGraph] is nodes and edges; [TaskGraph.runFrontier] is the whole
// scheduler. A node whose dependencies are all done is RUNNABLE; runnable nodes
// start until the concurrency cap is full and the rest wait on the frontier; a
// node that finishes unlocks its dependents and its report becomes part of
// their briefs. What v1 exercises is a graph of one node with no edges, and
// nothing here is written for that case: when decomposition lands it adds EDGES
// INTO THIS EXECUTOR, not a second machine, and the only thing that changes is
// that depends_on stops being empty.
//
// THE BRIEF IS ASSEMBLED WHEN THE NODE STARTS, not when it was proposed. That
// is the point of doing it here: a dependent's prerequisites have run by then,
// so what it is handed is its own brief plus what the work before it learned —
// facts that did not exist at proposal time.
//
// ── WHY A WORKTREE ──
//
// A node edits files while the person is editing files. Sharing a checkout
// would mean the node's half-finished sweep is what the person's `go build`
// compiles, and a node killed mid-edit would leave its wreckage in their tree.
// So each node gets `git worktree add` on a branch off the person's current
// HEAD: it works in a directory of its own, and the work comes home as a MERGE
// — clean, or a conflict that keeps the branch and says so. A workspace that is
// not a repository has no such isolation to offer, and the node runs in place
// rather than pretending (Merge is "inplace", and the person is told).
//
// ── A NODE STOPS BY A NAMED THRESHOLD, NEVER BY WANDERING ──
//
// Four things end a node's work, and every one of them has a name the person
// can read: its deadline, its step budget, its no-progress count, and a run of
// actions that produced nothing that was not already there
// (the implementation §1 — Argus terminates on named thresholds rather
// than on a reviewer's judgement, arXiv:2608.05144). The fourth is the only one
// that does not end anything on its own: reaching it is what makes the harness
// ASK, with the count and the working copy in front of whoever is asked
// (effects.go). A node that has taken
// forty steps or has taken six in a row without changing a file is not working,
// it is circling, and the difference between a harness that says "stopped: 6
// steps without progress" and one that lets the thirty-minute deadline collect
// it is half an hour of the person's money and a report that explains nothing.
// Both thresholds are per-node overridable on the wire (task.go's max_steps and
// no_progress) because the right budget for a one-file rename and for a sweep
// across forty files is not the same number.
//
// ── AND IT IS NEVER THE NODE THAT SAYS IT IS DONE ──
//
// A finished run is a CLAIM. What turns it into TaskDone is an independent
// read-only auditor with hard evidence (task_audit.go): VERIFIED merges,
// REFUTED is a TaskFailed carrying the auditor's evidence, and the cascade
// below fails its dependents with it. Nothing in this file writes TaskDone off
// the child's own last words.
//
// And when the auditor gives NO verdict — twice, having been asked again — the
// node lands TaskUnverified: not done, not failed, branch kept, dependents
// waiting rather than cascading, until a person resolves it
// ([Agent.ResolveUnverified]). A false failure is still a failure, and this
// file must not manufacture one out of an audit that never happened.
//
// ── WHY A NODE NEVER ASKS ──
//
// There is nobody to ask. Its approval posture is composed from the same
// [approval.Policy] the conversation uses — allow everything, with the critical
// table (rm -rf /, mkfs, a redirect onto a raw disk, shutdown) still a floor
// under it — and where a conversation would raise a question, the node reads a
// refusal it can act on. That is exactly consent.go's law for a headless run,
// said in the node's own words.
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/Agent-Field/agentfield/sdk/go/ai"
"github.com/Agent-Field/codeaf/internal/approval"
"github.com/Agent-Field/codeaf/internal/cell"
"github.com/Agent-Field/codeaf/internal/effort"
"github.com/Agent-Field/codeaf/internal/exec/bare"
"github.com/Agent-Field/codeaf/internal/guard"
"github.com/Agent-Field/codeaf/internal/provider"
"github.com/Agent-Field/codeaf/internal/roles"
)
const (
// taskDeadline bounds one node's whole life. An hour is a long build, the
// reading around it, and the audit after it; past that the node is not
// working, it is stuck, and a stuck node that never ends holds a slot and
// a worktree forever. The deadline is the LEASH, not the budget: the
// auditor is what says a node is done.
taskDeadline = 60 * time.Minute
// tasksDirName is where the worktrees live in the LEGACY layout: beside the
// job logs and under the repository, so `git worktree list` and a person's
// file browser both find them where they were left.
//
// A session that has a folder of its own puts them in [Place.Trees] instead
// (Decision 26). Git registers every worktree in .git/worktrees whatever its
// path, so repo-local placement was never a constraint — it was only where
// the first version happened to put them, and it is litter in somebody
// else's repository.
tasksDirName = ".codeaf/tasks"
// legacyTasksDirName is a PERSISTED worktree and lock location. New
// repository-local work goes to tasksDirName, but an earlier binary and this
// one must still meet on the same registered worktree and repository lock.
legacyTasksDirName = ".aforge-v3/tasks" // legacy-name
// taskReportLines and taskReportLineLimit bound the ordinary report. Two or
// three lines is what a person reads off a finished card and what a
// dependent's brief can afford to carry; the whole story is in the node's
// journal.
taskReportLines = 3
taskReportLineLimit = 300
// taskReportFenceLines is how much further a report may run to finish a
// fenced block it opened. A REPORT CARRIES WHAT IT PROMISES, but only a short
// quoted tail belongs on the finished card rather than in the node's journal.
taskReportFenceLines = 8
// taskReportCut tells the reader that the node said more than the bounded
// report could carry. It stands alone so it cannot become part of a fence.
taskReportCut = "…"
// taskSlugLimit keeps a branch name readable in `git branch`.
taskSlugLimit = 32
// taskMaxSteps and taskNoProgress are the two named thresholds a node stops
// by, counted in the only unit this side of the wall can see: the child's
// finished tool calls. They are the LEASH, not the budget — the auditor is
// what says a node is done, and a healthy node never meets either number.
//
// Two hundred steps is liberal on purpose: a real task reads, builds, fixes
// and verifies, and a step cap that bites during honest work is how you get
// "aborted" on a node that was about to finish. Six consecutive steps with
// no NEW information and no new dirt is the shape of a spin — the detector
// reads novelty now, so it only fires when a node is genuinely re-treading
// the same call, and it fires in a minute rather than in thirty.
//
// SIX IS NOT RETUNED FOR A FAST WORKER. A node calling a tool every three
// seconds spends six dead steps in eighteen of them, which reads like a hair
// trigger and is not one: the number bounds what is SPENT — six model
// round-trips and six tool calls that added nothing — and a worker that is
// cheap per step is not thereby entitled to more of them. What was wrong in
// the runs that made this comment was never the six; it was three shapes of
// real work being counted as dead ([addedSomething]).
taskMaxSteps = 200
taskNoProgress = 6
// Four renewals plus the original allowance make five equal budgets: 1000
// steps or five hours by default, and five times a wire max_steps value.
taskMaxExtensions = 4
// taskDepthLimit is how many tasks deep the tree may go, and taskFanLimit is
// how many sub-tasks ONE node may hand out. They are the two bounds on
// decomposition (task.go's fan-out law), and each answers a different way
// for it to run away.
//
// NEITHER IS WHAT DECIDES HOW WIDE WORK GOES, and until 2026-09-11 both were written as
// though they were. Five children was a guess about decomposition ("more than that has
// not decomposed its work, it has shredded it") and two levels a guess about where a
// third stops paying, and nobody had measured either. What actually bounds how much runs
// at once is the machine and the work: the person's task.parallel holds a ready node
// until a slot frees, the admission governor holds one while the machine is already
// loaded (task_pressure.go, which weighs the machine once per frontier pass, so a batch
// that becomes ready together is admitted on one reading), and two parts that would write
// one file are refused (a division) or queued one behind the other (quick tasks' file
// claims), not counted. Whether a worker SHOULD split is the fan-out page's question
// (prompts/fanout.md) and the division road's evidence gate (task_divide.go), each asked
// of the material in front of it; a constant here cannot know the answer for work it has
// never seen. Breadth may be large where the work parallelises, because a node's
// parts cost the longest of them and not their sum.
//
// SO THE FAN CAP IS A RUNAWAY STOP AND NOTHING ELSE. A node handing out more
// than twenty pieces has lost the plot rather than found the width, and it
// still has to fold every one of their reports into one deliverable. It is a
// REFUSAL the model can read ([TaskGraph.claimChild]), not a queue, so the
// answer to a twenty-first part is to do it in its own hands.
//
// THREE LEVELS lets a part of a wide job fan once more: the conversation's
// task hands out its parts, and a part that opens its material and finds it
// wide in turn may split its own share rather than grinding through it. The
// floor is still absence and not refusal (task.go's [Config.mayFanOut]), and
// every level still costs a worktree, a check and a wait, which is why the
// pages that teach splitting say sequential work is never split.
taskDepthLimit = 3
taskFanLimit = 20
)
// The three things a node can be waiting on, spelled once. They are the
// strings [TaskNotice.Waiting] carries, and every one of them is a word about
// the WORK rather than about the machinery that produced it: a person reading
// a card wants to know their task is not moving and roughly whose fault that
// is, and "429", "AIMD" and "governor" are three ways of telling them about
// this package instead.
const (
// waitingSlot is the person's own task.parallel cap holding a ready node.
waitingSlot = "slot"
// waitingMachineBusy is the admission governor holding it: this machine is
// carrying more than the load or the memory floor allows (task_pressure.go).
waitingMachineBusy = "machine busy"
// waitingRateLimited is a RUNNING node whose call is parked on the
// provider's pacing (internal/provider's patience.go). It is the only one of
// the three that is true of a node with a worktree and a child agent.
waitingRateLimited = "rate limited"
// waitingOnItsParts is a RUNNING node that handed its work out and is parked
// on the reports of the parts it handed out ([TaskGraph.park]). It is the
// PARENT-STAYS state said out loud: the node is not finished, nothing has
// gone wrong with it, and it is deliberately not being asked anything until
// its parts are in (task_divide.go's parent-stays law). Before this it was
// drawn as a node simply running, which is the one reading a person must not
// be left with — a row that has said nothing for four minutes and a row that
// is waiting on three workers look identical, and only one of them is worth
// worrying about.
waitingOnItsParts = "its parts"
)
// The three merge outcomes, the kept landing, and the mark that says a run
// stopped before its branch came home.
// They are the strings [TaskNotice.Merge] carries, spelled once.
const (
mergeMerged = "merged"
mergeConflicted = "conflicted"
mergeInPlace = "inplace"
mergeAborted = "aborted"
mergeKept = "kept"
)
// ── the graph ───────────────────────────────────────────────────────────────
// TaskNode is one piece of work: what it was admitted with, where it is in its
// life, and what it leaves behind.
//
// Every field below the mutex line is guarded by the GRAPH's lock, not one of
// its own. A node is never touched alone — starting one reads its dependencies'
// reports, finishing one unlocks its dependents — so a second lock would be a
// lock ordering to get wrong for no gain.
//
// ── THE GOAL CONTRACT: spec IS FROZEN AT ADMISSION ──
//
// NOTHING IN THIS FILE WRITES spec.request, spec.brief, spec.deliverable OR
// spec.acceptance AFTER admit — the four parts of what the node is told
// (task_brief.go). Not the frontier, not the runner, not the auditor, not a
// redirect that arrives late. The node's goal is settled the moment
// [TaskGraph.admit] takes it, and
// every reader downstream — the instruction the child is given, the acceptance
// the auditor judges against, the report a dependent inherits — reads THAT text
// and no other.
//
// This is Argus's two-tier goal contract (the implementation §1,
// arXiv:2608.05144), and the two tiers are drawn exactly here: the semantic
// tier moves freely BEFORE admission — the model grooms the brief, the person
// redirects it and their words are appended (task.go) — and the precise
// objective moves only with authority, which in this surface means a NEW
// admission by the person. A redirect is not an edit to a running node; there
// is no path to one, and there must not be. The reason is the auditor: a
// frontier that verifies work against an acceptance which can move while the
// work runs verifies nothing, because whoever holds the pen can always make the
// work pass. What the person gets instead is honest — the running node lands
// against what it was given, and the correction is a task of its own.
//
// The only fields below that change after admission are the node's LIFE (state,
// started, cancel) and its LEAVINGS (brief, report, changed, branch, merge).
// brief is assembled once at start (runFrontier's JIT assembly) from spec.brief
// plus prerequisites' reports and is not the spec.
type TaskNode struct {
// storageBlocked records a writer whose bounded shutdown has not finished.
storageBlocked bool
// Continuation choices preserve the completed attempt until work is reopened.
nextModel string
nextEffort *string
// picked is THE MODEL A PERSON NAMED FOR THIS NODE THAT IT HAS NOT RUN ON
// YET, and "" the rest of the time. It is written by
// [TaskNode.retargetLocked] — the only post-admission spec write there is —
// and cleared by [TaskNode.startedOn] when a worker is actually built on it.
//
// IT IS A FACT AND NOT A COMPARISON, and that is the whole of why it is a
// field. It was a bool beside the spec's id, and "is the pick still owed"
// was then derived by comparing the spec against the model the node was
// running on — which the pick itself had just rewritten, so the answer was
// no from the instant the person chose. Only a LATER rescue, after something
// else had moved the node off the pick, could see it. The person's own
// scenario — pick a model over a step that is grinding, and have the next
// move go there — was exactly the one the derivation could not answer.
//
// It is in-memory on purpose; see [TaskNode.standingModel].
picked string
graph *TaskGraph
id uint64
dependsOn []uint64
// parent is the node this one was handed out BY, and 0 for the work a
// conversation proposed. It is the family seam [TaskNotice.Parent] carries,
// and it is not an edge: dependsOn says what must finish first, this says
// who asked (task_contract.go states the difference).
parent uint64
// admitBy and admitAt are WHOSE REQUEST HANDED THIS WORK OUT: the agent that
// admitted the node and the request it was working on at the time — its turn
// and the person's steers into that turn ([requestEpoch]) — or nil and the
// zero epoch for a node nobody's turn opened: a checkpoint restore, a
// scripted graph, a `/task` typed between turns. They are written once, at
// admission, beside the rest of the node's provenance, and read by
// turnhandoff.go so the turn that handed its ask off is not read as a turn
// that stopped short of it.
admitBy *Agent
admitAt requestEpoch
// depth is how many tasks deep this node sits — 1 for the conversation's
// own, 2 for a sub-task, 3 for a sub-task's own part — and it is what
// taskDepthLimit bounds.
depth int
// owner is the agent that RUNS this node: the conversation for a root, and
// the PARENT NODE'S OWN AGENT for a sub-task. That is the whole of the
// nesting: a sub-task's worktree branches off its parent's worktree and
// merges back into it, so a family's work comes home as one branch rather
// than as five racing for the person's.
//
// It is nil for a node rehydrated from a checkpoint — the agent that owned
// it died with the process — and for every scripted graph in the tests. Both
// fall back to the conversation, which is the only agent still there to run
// anything (see [TaskGraph.runner]).
owner *Agent
// done is closed when the node reaches a final state. It is how a waiter —
// a test, a future join — waits without polling.
done chan struct{}
// admit serializes the admission of NAMED corrections to this node, and it
// is the whole of how "written down before it is delivered" holds under two
// requests at once (task_room.go's admission law). It is not the graph's
// lock and must never be taken while holding it: the checkpoint inside this
// section takes store.mu and then graph.mu, and the ordering
// admit → store.mu → graph.mu is the only one this file uses.
admit sync.Mutex
spec taskSpec
// kind is what sort of node this is ([TaskKind]), settled at admission from
// the spec and never touched again. It is a FIELD rather than a question put
// to the spec because a node restored from a checkpoint has no spec worth
// asking — the design it was written from is gone, and the kind is the one
// word its history still needs (task_store.go).
kind TaskKind
// Ground is the repository or folder THIS WORK IS ABOUT, absolute, and Mode
// is how the node stands on it ([TaskMode]). They are settled before the
// node's first tool call — at the door for work somebody proposed
// (taskstands.go), and by the working copy itself for everything else — and
// they never move afterwards.
//
// THEY ARE WHAT EVERY OTHER PART OF THE MACHINERY ASKS. The guard asks which
// directory a write may land in, the audit asks which repository a clean
// restore is cut from, a card asks which project to name, and a checkpoint
// carries them so a resumed node knows the same thing this one did. Before
// they existed each of those answered for itself out of the worktree it
// happened to hold, and issue #76 is what that cost: four parts of one
// program disagreeing about which repository an hour of work was for.
//
// They are exported among unexported neighbours because they are read from
// every one of those places by name; like the fields around them they are
// guarded by the graph's lock and written once.
Ground string
Mode TaskMode
// These identities bind restored legacy and nested task worlds to their owners.
WorkspaceURI, ChatURI, ProjectURI, GroundURI, RootURI string
// Home is the branch the root checkout was on when this node's branch was
// cut. It is the fixed side of the moved-checkout question at landing time;
// absence is ordinary for a checkpoint written before this field existed.
Home string
// HomeSha is the commit Home named at the cut. The branch name answers which
// destination the person chose, but only this commit answers which world that
// name held before they and the task went on working independently.
HomeSha string
// Rung is which rung of the ground ladder made this node's world and Seal is
// the one string that names that world — furrow's sealed snapshot, or the
// machine commit's sha (groundladder.go). They are written beside Ground and
// Mode by [TaskNode.setTree], from the tree that was actually made, and they
// are what lets a report say what world the work was done in.
Rung GroundRung
Seal string
// Frozen is THE WORLD THIS NODE STARTS FROM, when it is a part of a family
// that froze one: the commit its parent's division put the family tree at
// before any part of it was admitted (task_divide_wip.go). The ground ladder
// carves this node's working copy from it and seals nothing.
//
// WITHOUT IT THE SIBLINGS GET DIFFERENT WORLDS. A part's working copy is
// prepared lazily, when the frontier starts it, and a parent goes on working
// while its parts run — so two parts cut a minute apart would each inherit
// whatever the parent's directory happened to hold at that instant, and the
// division that named their boundaries would have described neither.
//
// IT IS THE CHILD'S OWN FIELD AND NOT A LOOKUP ON THE PARENT, which is the
// difference between a fact and a variable: a parent may divide more than
// once, and a second division reading a field the first one wrote would put
// the new parts in the old world — or, worse, move the old parts' world under
// them. It is written at admission, from the spec, and never again.
//
// IT IS ON THE CHECKPOINT (task_store.go) because a resumed part must not
// reseal: coming back after a restart and inheriting the parent's tree as it
// stands NOW would be the same divergence arriving through the one road that
// does not prepare its tree at the door.
Frozen string
// Family is THE CHECKS THIS NODE OWNS FOR THE WHOLE FAMILY IT HANDED OUT: a
// check that every part of a division was told to run, taken off all of them
// and given to the one node that can honestly make it — this one, once, after
// every part's work is home (task_divide_scope.go).
//
// IT IS A FIELD RATHER THAN A WRITE TO THE SPEC, and that is a law and not a
// convenience. NOTHING IN THIS PACKAGE WRITES A SPEC AFTER ADMISSION — the
// contract a node was admitted with is the contract it is judged against, and
// [TestEachPartCarriesItsOwnDoneConditionAndTheParentKeepsTheOriginal] pins
// it. So the repair does not edit `spec.acceptance`; it puts what it lifted
// HERE, where the reader that needs it can find it: the worker is TOLD what it
// owns ([TaskNode.instructionOn]) and runs it in its own hands. What its
// CHECKER may re-run is the narrower [TaskNode.FamilyDeclared], for the reason
// stated there.
//
// IT IS ON THE CHECKPOINT (task_store.go), UNLIKE [TaskNode.sharedTold], and
// the two go opposite ways for one reason: the telling is about a
// conversation that is over, and this is about a run that has not happened
// yet. The parent's own check is made after every part is home, which can be
// hours later and a different process from the one that divided — a resumed
// node that had forgotten it would be a check nobody ever makes.
//
// It is exported among unexported neighbours for [TaskNode.Ground]'s reason:
// it is read by name from more than one place. Like them it is guarded by the
// graph's lock.
Family []string
// FamilyDeclared is the part of [TaskNode.Family] this node's CHECKER may
// re-run: the family's checks that a part had DECLARED as verification rather
// than ones this package recognised in a part's prose done-condition
// ([declaredAmong]).
//
// THE TWO LISTS ARE TWO PERMISSIONS AND THAT IS WHY THERE ARE TWO. Family is
// what the parent's worker is told to run once its parts are home, which is
// ordinary work in ordinary hands. This is executable verification, and
// lifting must move it rather than mint it: a command nobody ever typed into
// `checks` was executable verification nowhere, so making it the parent's door
// would be the prose harvest this build removed, arriving by the family road.
//
// A CHECKPOINT WRITTEN BEFORE THIS FIELD CARRIES NEITHER PROVENANCE NOR THIS
// LIST, and it is read as declaring nothing. Old records were filled by a
// build that harvested commands out of prose; granting them fresh execution
// now, on the strength of a list nobody can tell apart any more, is the one
// thing this field exists to prevent.
FamilyDeclared []string
// FamilyWas is what this node was required to run for its family BEFORE the
// goal moved. A revision takes the family's checks off the node — both the
// half its checker could run and the half its worker was told to run — because
// an instruction written for the old goal standing in front of the new one is
// the contradiction a person reading the card would have to resolve. Keeping
// them here rather than dropping them means what was required is still legible
// afterwards, on the node and on the checkpoint, without being required.
FamilyWas []string
// Checks is THE REPEATABLE VERIFICATION THIS WORK WAS PUT UNDER CONTRACT WITH:
// the commands whoever proposed it declared as the way anybody re-establishes
// that it is done, typed into `checks` rather than harvested out of prose
// (task.go's schema, task_checks.go's law). They are the ONLY commands this
// node's checker may run, beside the reading ones, and an empty list — which is
// most nodes, and every node admitted before this field existed — means the
// checker judges from what it can see and from the worker's receipts.
//
// IT IS A NODE FIELD AND NOT ONLY A SPEC ONE for the reason [TaskNode.Family]
// is: a check can be moved by something that happens after admission. The spec
// carries what the proposal declared, this carries what the node is checked
// against NOW, and [TaskNode.reviseChecks] is the one door between them —
// because a steering revision that changes the goal must not leave the old
// goal's checks standing in front of the new one.
//
// It is on the checkpoint (task_store.go) for Family's reason: the check is
// made when the work comes home, which can be a different process from the one
// that admitted it. Like its neighbours it is guarded by the graph's lock.
Checks []string
// checksRevision is WHICH REVISION OF THE ASSIGNMENT the two lists above were
// written for. Admission leaves it 0, which is the revision of a node nobody
// has steered; [TaskNode.reviseChecksLocked] writes it in the same hold of the
// lock that writes the checks, and [auditDoorForRevision] refuses to build a
// door out of checks stamped for a revision other than the one the audit is
// judging. It is on the checkpoint beside them, because a stamp that did not
// survive a restart would make every restored node's checks read as current
// under whatever the assignment says then.
checksRevision uint64
// Base is the machine commit the parent's world was sealed into and Universe
// is furrow's name for the fork, when a rung made either. They are here for
// the SAME REASON Rung and Seal are — the landing needs them and the landing
// does not always happen in the run that made the world. A person who accepts
// a task hours later, or a session resumed after a crash, rebuilds the
// working copy from these four fields ([TaskNode.workingCopy],
// [TaskNode.resumeTree]); without them the inheritance would be merged back
// over the person's own edits and a branch in a fork would be looked for in
// the wrong repository.
Base string
Universe string
// CheckBase is the immutable Git commit captured before the worker runs.
// A universe's Seal names a filesystem snapshot, not a Git commit.
CheckBase string
// Expects is THE CHECKABLE HALF OF THE HANDOFF this node was given: what its
// brief assumes is already true of the world it gets (handoffcontract.go).
// It sits beside Ground for the same reason Rung does — the ground says
// which world, and this says what the world was promised to contain — and it
// is written once, at admission, from the spec.
//
// The contract is answered before the node's first step, so this is restored
// from a checkpoint only onto a node that has NOT yet run (task_store.go's
// [expectsOwed]); one that started has been through it, and handing it the
// manifest again would put a settled question to a working copy its own work
// has since changed. The spec's copy survives either way, because the brief
// carries it as a section.
Expects []Expectation
brief string
// adjudicated says this node has already spent its one tiebreak: a division
// the evidence gate refused on the floor has been put to the mastermind once
// on the strength of a judge's wide reading, and the answer — whatever it was
// — is the answer ([TaskNode.takeTiebreak], task_divide.go). It is guarded by
// the graph's lock like every other field a worker's goroutine touches, and it
// is deliberately NOT on the checkpoint: a restart loses the judge's reading
// too (task_store.go re-arms from the text alone), so a node that comes back
// cannot reach this path at all.
adjudicated bool
// sharedTold says this node has already been told once that its parts were
// each ordered to run one check (task_divide_scope.go). The FIRST telling is
// the refusal, which is the answer a worker that can redraw its
// done-conditions acts on; a SECOND firing of the same rule on the same node
// is a worker that cannot, so the harness lifts the check onto this node
// instead of refusing again.
//
// IT IS GUARDED BY THE GRAPH'S LOCK like every other field a worker's
// goroutine touches, and it is deliberately NOT ON THE CHECKPOINT for exactly
// [TaskNode.adjudicated]'s reason: a restart loses the telling too, so a node
// that comes back cannot reach the repair road without being told again. The
// repair spends a worker's own second ask; a node that never had a first one
// must not inherit the answer to it.
sharedTold bool
state TaskState
// report, changed, branch, worktree and merge are the node's leavings,
// written by the goroutine that ran it and read by everybody else. worktree
// is where it worked, and it is kept for two readers: a recovery that has
// to tell the person where an interrupted node's half-finished work is
// (task_store.go), and a continuation that re-arms the same node
// ([Agent.ContinueTask]).
report string
changed []string
// landed is the report THE LANDING ITSELF WROTE, and late is every message
// about this node's own pieces that came home after its worker had stopped
// reading (task_latefold.go). They are two fields rather than one because the
// report above is composed out of both and is recomposed whenever either
// moves: a landing that overwrote the report would otherwise erase a piece's
// result, and a fold that appended to it would be a second author of one
// sentence. Both are on the checkpoint ([taskRecord.Late]), so a restored node
// composes from the same halves a live one does.
landed string
late []string
// wrote is every path this node has written SO FAR, in the order it first
// wrote them and capped at [taskFilesLimit]. It is the LIVE half of changed,
// which does not exist until the node lands: a node writes for eleven minutes
// and only then says what it wrote, and by that time the one thing another
// window could have done about it — not open the same file — is over.
//
// So this is held up in the session's presence file ([Agent.presenceTasks])
// while the work is happening, refreshed by the ordinary heartbeat.
//
// IT IS A FACT AND NOT AN INTENT. A path is added when a saving call has come
// back successful and never because the node said it meant to write
// something, which is the same bar changed is held to.
wrote []string
// receipts is the tail of what the node's last worker actually RAN, kept for
// the one reader that never watched it happen: the auditor
// ([lastToolReceipts], task_audit.go). It is not in the checkpoint and it is
// not drawn anywhere — it is evidence for one question, refreshed by whichever
// worker spoke last, and a resumed node simply has none.
receipts []toolReceipt
branch string
worktree string
merge string
// clashing names the files that stopped this node's branch fastening onto the
// person's, read out of the index while the refused merge still held them and
// written here by the landing road ([landHome]). It is what a your-call row
// says out loud — "conflicts with your branch: parser.go" — rather than making
// a surface read the names back out of the report's prose.
//
// IT IS GUARDED BY THE GRAPH'S LOCK like every other field a landing writes,
// and it is empty on every node whose branch went home and on one where git
// would not say which files it was about.
clashing []string
// shifted says the names in clashing were put there by THE GROUND MOVING and
// not by a refused merge ([Agent.landShifted], taskground.go): the branch
// would fasten, and the person's own branch changed those same files while
// this node worked.
//
// IT IS THE FACT THAT TELLS THE TWO ROADS APART, and it is a field rather
// than a reading of the sentence because a surface parsing prose to work out
// which question it is asking is this program reading its own writing. Both
// roads ask the same question — two versions of these files, which survives —
// so both land on [TaskAskConflict]; only the reason sentence differs
// (task_status.go's [taskShiftReason]).
shifted bool
// groundHeld says the names in clashing are the PERSON'S OWN UNTRACKED COPIES
// of the files this task wrote, sitting in the folder the branch merges into
// (groundcarry.go). It is the third road to one question — two versions of
// these files, which survives — and it is the only one of the three whose
// `resolve it` is a carry rather than a merge round, because a file git is
// not watching is on no branch for a round to merge.
groundHeld bool
// decider is WHO HOLDS THIS NODE'S DECISION ([TaskAskOwner]), for the one
// state that has a decision in it. The person is the answer everywhere except
// under `task.settle = auto` and after somebody has handed this one card over
// ([Agent.HandUnverifiedToModel]).
//
// IT IS ON THE CHECKPOINT (task_store.go's [taskRecord.Decider]), and what a
// restart does with it is the floor rather than forgetfulness: a node that
// comes back from disk saying the model was holding it is handed to the person
// on the way in ([TaskGraph.handBackOnLoad]), because the turn the model was
// going to decide in died with the process and no other turn is going to run
// for it. It used to be kept off the record on the argument that the zero
// value read as the person anyway — which was the right answer arrived at by
// accident, and it left the floor with nothing to fire on, no fixture able to
// seed a card codeaf is holding, and an engine that died mid-turn quietly
// dropping the hand-back it owed.
decider TaskAskOwner
started time.Time
// ended is the instant this node last landed. It is stamped at the live
// transition and kept on the checkpoint, so a rebuilt row reads the record's
// fact instead of dating old work with the clock that happened to read it.
ended time.Time
// elapsed is the node's age FROZEN at the moment it landed. While it runs it
// is zero and the age is measured from started; after a restore this is the
// duration the record carried beside its original start and landing stamps.
elapsed time.Duration
// cost is what this node's agents have spent, in dollars, ACCUMULATED where
// each of them is closed ([Agent.foldTaskUsage]). While the node runs it is
// zero and the live figure is asked of the child directly (see
// [TaskNode.spend]); after it lands this is the frozen answer, because the
// node outlives its child and a surface asking a landed node what it cost
// has nobody else to ask.
//
// IT IS IN THE CHECKPOINT (task_store.go), and it is a figure somebody can
// point at. The argument against keeping it used to be that a resumed graph
// is a graph whose child agents are gone, so a number rehydrated from a file
// would be the only one on the task index nobody could point at a request
// for. That dissolved when every agent started journaling a `usage` line per
// call (sessionfile.go): the node's own transcript now holds the lines this
// total is the sum of, and a person who doubts the figure can open them. It
// reaches the project index (task_index.go) and the node's own notice on the
// way past.
cost float64
// checkedOn is the model the node's checking pass ran on, read off the
// auditor when its spend folds in (task_audit.go) and empty when there was
// none — a node that never reached a check, or one whose check could not
// start. It is a fact about the last check, not a setting, and it is read by
// the landing hook ([Agent.reportTaskNode]) beside the worker's own model.
checkedOn string
// input, output, cacheRead and cacheWrite are that same accumulation in
// TOKENS, folded in beside the dollars and kept on the checkpoint with them.
//
// They are held rather than derived because a price is a claim about a
// moment — what the model charged when the call was made — while the tokens
// are what happened. A node priced by a provider that later changes its
// rates, or run on a model nobody published a price for at all, still has
// these; a bill kept in money alone could never be worked out again.
input int
output int
cacheRead int
cacheWrite int
// attempt counts the lives of this node's work: 0 for the run it was admitted
// for, one more for each time it is re-armed ([TaskGraph.reopen]). It is the
// generation half of an announcement's identity, because a delivery of the
// last attempt's ending can still be in flight when the next attempt starts
// ([TaskNode.claimNote]). Absent from checkpoints written before it existed,
// which restore as attempt 0 — the life they were written in.
attempt int
// noted says this node's completion note is on a live reader's QUEUE, and
// notedState the ending it was handed over for: the same landing announced
// twice is one piece of news, while a node that later ends somewhere else
// ([TaskGraph.resettle]) is news again. It is what a parked parent reads as
// "this piece is no longer outstanding" ([TaskNode.reported]), and it is held
// in memory only, because a queue does not survive the process it lives in.
//
// notedRead says the RECIPIENT'S OWN RECORD holds it, and that is the one
// that is written down ([taskRecord.Noted]): a note queued onto an idle
// conversation nobody is attached to is read at a step boundary that may
// never come, and marking it announced on the checkpoint is how a landing
// went missing for good ([durableDelivery]).
//
// Both belong to the CURRENT attempt, because reopening clears them in the
// same locked step that raises it. An empty notedState is a checkpoint
// written before it existed and reads as "announced, whatever it said".
noted bool
notedRead bool
notedState TaskState
// noting holds the claim a delivery in flight took, so two deliveries of one
// ending cannot both reach the queue. It is in memory only: what is written
// down is the completed hand-over, never an intent.
noting bool
notingClaim noteClaim
// interrupted marks a node that was RUNNING when a session ended and whose
// interrupt a recovery has consumed. It is history, and it is written down so
// that exactly one recovery ever consumes it.
interrupted bool
// continuing marks a node a person (or the model) re-armed after it
// settled, so [TaskNode.resumeTree] reuses the kept branch instead of
// cutting a fresh working copy ([Agent.ContinueTask]; F23/F25).
continuing bool
// finding is what a continuation adds THIS ROUND: the last attempt's
// report and any words sent with continue. It is not the assignment —
// spec.brief stays the original — and a second continue replaces it
// rather than wrapping it.
finding string
// checkedAt is the assignment version the finished work was judged at. The
// landing compares it with the version in force at the instant it takes the
// publication boundary, so work checked against a condition the person has
// since moved cannot be published as meeting it (assignment.go).
checkedAt uint64
// carried is the receipts written into this attempt's finding and not yet put
// in front of a model: the opening request marks them read (assignment.go).
carried []uint64
// assignment is what this node is working towards NOW: every line said to
// it, and the revisions the person's own lines were allowed to make
// (assignment.go). The zero value is a node nobody has said anything to,
// whose effective assignment is the spec it was admitted with — which is
// every node restored from a checkpoint written before this existed.
assignment taskAssignment
// publishing marks the instant a landing took the publication boundary: from
// then until the node settles, a direction is recorded and told it belongs to
// the round after this one, because the merge it would have changed is
// already going out (assignment.go's [TaskNode.claimPublication]).
publishing bool
// directedRounds counts how many times the person's own words sent this node
// round again inside one run, and is what [directedRoundLimit] bounds.
directedRounds int
// reruns counts the attempts THE ENGINE bought itself for an ending that said
// nothing about the work — a dropped connection, a provider that would not
// serve the request, a brief whose world had moved — and is what [rerunLimit]
// bounds (task_continue.go). It is a different counter from directedRounds
// because it answers a different question: nobody asked for these rounds.
reruns int
// mergeRounds counts the rounds spent trying to resolve a branch that would
// not fasten onto the person's, and is what [mergeRoundLimit] bounds
// (task_merge_round.go). resolving marks the one that is running, so a second
// ask while one is in flight is refused rather than run beside it.
mergeRounds int
resolving bool
// resolvingGen and settleGen are the CLAIM'S IDENTITY, not its words: two
// accepts say the same thing ("your accept"), and a release that matches
// words cannot tell its own claim from a newer one's — the deferred
// release of the first would wipe the second’s guard mid-flight. Each
// claim takes the next number, and a release hands the claim back only
// when its own number is still the live one ([TaskNode.releaseSettle],
// [TaskNode.releaseResolving]).
resolvingGen uint64
settleGen uint64
// offer is a finished harness page waiting on the person, held for exactly
// as long as its card is up so the checkpoint can carry it across a restart
// ([TaskNode.carryOffer], task_store.go's harnessOfferRecord). Nil on every
// node that is not a design mid-question.
offer *harnessOfferRecord
// queuedSaid marks that this node's "queued" update has been sent. A node
// waiting behind the cap or behind an edge must show up on the surface —
// otherwise admitted work is invisible until it starts — but a frontier pass
// runs on every completion, and a node that announced itself on each of them
// would be a card redrawing itself for news that has not changed. So the
// queued update is sent once, and again only when [TaskNode.held] changes
// under it, which is news.
queuedSaid bool
// held is why the frontier is not starting this queued node, in the plain
// words [TaskNotice.Waiting] carries: waitingSlot, waitingMachineBusy, or ""
// for a node whose own edges are the answer — a dependent waiting on its
// prerequisites has nothing to say here that DependsOn does not already say.
//
// It is written by the frontier and cleared the moment the node starts or
// fails, so a landed node never carries a reason it is waiting.
held string
// parked says this node has HANDED ITS LANE BACK while it waits on the work
// it handed out (see [TaskGraph.park]). It is true only between a parent's
// turn ending and its next report arriving, and it is what keeps a family
// from deadlocking against the person's own task.parallel cap.
parked bool
// parkGen counts the parks this node has taken, and it is what makes ONE
// park tellable from the next. `parked` alone cannot: a parent wakes on a
// report, reads it, finds a part still outstanding and parks again, and to
// anybody watching the flag from outside those are the same true. A waiter
// that wants the park AFTER a landing therefore has to name the park it
// already saw and wait past it ([TaskNode.parkStanding]), which is a fact
// about identity and not about how long to wait — no clock can answer it.
//
// It only ever goes up, one per park, under the graph's own lock, and it is
// never reset: a node that parks and lands and is resumed is still the node
// that parked, and a generation that started again would make an old park
// look like a new one.
parkGen uint64
// paced counts this node's calls that are parked on the provider's pacing
// (internal/provider's patience.go). A count and not a flag because a node
// is an agent and an agent can have more than one call out — a repair round
// beside an audit — and the node stops being paced when the LAST of them
// gets through, not when the first does.
paced int
// beat is this node's pulse on disk while it runs, and nil for a node that
// is not running or has no store behind it (task_beat.go). Every agent that
// stands in for the node carries a pointer to the same one — the worker, the
// checker, each repair round — because they are one node working, and a
// reader outside the process is asking about the node.
beat *taskBeat
// ctx is the context this node's whole run happens under and cancel ends it:
// cut by jobs kill, by a person's stop, or by Close.
//
// BOTH ARE MADE WHEN THE NODE IS STARTED AND NOT INSIDE THE GOROUTINE THAT
// RUNS IT ([TaskGraph.runFrontier]), which is the law issue #381 was: a node
// that becomes reachable only from inside its own goroutine is a node a
// quit cannot see for as long as that goroutine takes to be scheduled — and
// what it did in that window was open a log, build a child agent and spend
// on two model calls for a session that had already left.
ctx context.Context
cancel context.CancelFunc
// claimed says A RUNNER HAS TAKEN THIS NODE UP: [Agent.runTaskNode] sets it
// in the one hold of the lock that also reads the context it is about to run
// under ([TaskNode.claimRun]), and a test's stand-in for a run sets it
// through [TaskNode.setCancel]. It is never cleared, because a node is taken
// up once.
//
// IT IS THE QUESTION A STOP ASKS BEFORE IT PROMISES TO WAIT. That question
// used to be asked of `cancel` — nil meant nobody was running this node, so
// nothing was ever going to settle it and the stop settled it itself
// (cancel.go's [TaskGraph.stop]). The frontier now mints the cancel at
// admission so a quit can reach a node whose goroutine has not started yet,
// which left `cancel` unable to answer it: every running node has a handle
// now, including the ones nobody has picked up. So the fact is written down
// on its own rather than inferred from a pointer that no longer means it.
claimed bool
// stopped marks a node a PERSON ended (cancel.go). It is set BEFORE the
// context is cut, so that the landing this stop causes already knows whose
// decision it was — a flag written afterwards would be a flag the update
// announcing the end raced past.
stopped bool
// handed is the receipt for THIS SESSION'S own hand-over press: the person
// asked codeaf to decide this one card, and the note that asks it has been
// put in front of the model. It is not the same fact as [TaskNode.decider]
// being the model — a landing under `task.settle = auto` writes that by
// policy and presses nothing — and it travels with the owner through
// [TaskNode.givesBackLocked] so a hand-back leaves no receipt behind
// (task_audit.go's [TaskNode.wasHandedOver] states the whole rule).
handed bool
// stopReason is what whoever pulled the stop said they were stopping it FOR,
// and "" for every stop that came with no words — which is every one a person
// pulls, their card being a decision and not a sentence (cancel.go). It is
// written beside the flag and for the same reason: the landing this stop
// causes is the one place the reason can still be put on the record.
stopReason string
// ending is why this node stopped where it did, once it has (task_contract.go's
// [TaskEnding]), and "" until then and forever on a node that finished. THE
// FIRST CAUSE WINS: [TaskNode.end] refuses to overwrite one already written,
// because a check that refuses a run that had already given up is not the
// news — the giving up is.
ending TaskEnding
// checked is WHAT THE CHECK SAID about this node's work, in the ledger's own
// taxonomy, and "" on a node no check ever read — one whose worktree could
// not be made, one somebody stopped before the gate, one that ran with the
// check turned off. It is the grade a settled node teaches the ratings store
// (taskgrade.go), and it is written down at the moment the answer is given
// because that is the only moment anybody holds it: by the time the node
// settles, the whole of the check is a paragraph of prose in a report.
//
// repairs is how many times the work was handed back before that answer —
// the repair rounds the gate spent (task_audit.go's [Agent.auditWithRepair]).
checked provider.Reading
repairs int
// noDeclaredCheck and cleanGround are WHAT THE NODE'S OWN CHECK SAW, carried
// onto the node because that pair is known only inside the check and dies with
// it ([auditDoor.window]'s predicate, [groundManifestClean]). They are written
// when the check runs (task_audit.go) and read when the landing note is
// minted, where a node that has both needs one call and no more to settle
// ([TaskNode.settleCeiling], agent.go's [settleCallCeiling]).
noDeclaredCheck bool
cleanGround bool
// blockedBy names the task whose working copy refused this node's writes
// (treehold.go's treeClaimGuard), in the words the refusal used, and "" when
// nothing ever refused it. It is what turns a "going in circles" ending into
// a "blocked" one: a worker that was told no every time it tried to write is
// not circling, it is queued behind somebody, and the row should say whom.
blockedBy string
// room is the node as a PLACE: the child agent somebody can talk to and the
// live subscribers watching it work (task_room.go). It is nil until the
// first person enters or the runner attaches its child, and it is emptied
// when the node lands.
room *taskRoom
// journal is where this node's transcript was written, recorded when its
// child agent was built. It is the node's history, and it outlives the room
// — and the process: it rides the checkpoint (task_store.go's
// taskRecord.Journal), so a resumed session opens a finished task on its
// whole transcript rather than on a blank page.
journal string
// revise is the lane a DESIGN's thread asks for its page to be rewritten on,
// and nil on every other kind of node — which is what keeps the revise_design
// verb off every other thread's belt (harness_task.go's reviseDoor states the
// whole arrangement). It is minted once, before the thread agent is built,
// and it is read by exactly one goroutine: the design loop parked on the card.
revise chan string
// doing is the PHASE a node of a named kind is in, in that kind's own plain
// words — "designing", "awaiting your look" — and "" for an ordinary task,
// which has no phases and whose state word is the whole truth about it
// (harness_task.go).
//
// IT IS A REPLACEMENT AND NOT A DECORATION, which is the one thing that makes
// it different from mend and held below. Those two are said BESIDE "running",
// because a node closing a gap or waiting on a slot is running and a surface
// that spent the state word on either would be hiding the state. A harness
// being designed is running too, but "running" is a word about the machinery
// and "designing" is a word about the work — so the surface draws this
// INSTEAD, and the state underneath is unchanged.
doing string
// context is the NAMED WORKING CONTEXT this node's room is, in the words a
// person would use for it — "designing a subharness", and then "designing
// flake-triage" once the page it is writing has a name (harness_task.go) — and
// "" for a node whose room is only work being watched.
//
// IT IS ABOUT WHERE A PERSON'S OWN WORDS LAND AND NOT ABOUT THE WORK. A phase
// ([TaskNode.doing]) answers "what is this node busy with"; this answers "what
// am I part of when I say something in here", which is the question a surface
// drawing somebody's own line has to be able to answer. An ordinary task node
// is briefed and left to it, so there is nothing to be part of and this stays
// empty — the emptiness law, and what makes a surface that draws this draw
// nothing at all for every ordinary turn.
//
// IT IS NAMED BY THE NODE'S OWN BODY and by nothing else, which is what keeps
// the mechanism generic: a future kind of node that is also a place somebody
// talks inside names itself here and every surface already draws it, with no
// list of kinds anywhere. Like [TaskNode.doing] it is announced when it
// changes ([TaskNode.contextNow]), because a context that has just learned its
// name is news that arrives without the state moving.
context string
// mend is the gap a repair round is closing right now, in plain words, and ""
// at every other moment (task_audit.go's repairNode). It is the ONLY thing
// the repair loop puts on the wire while it runs: the node is still running,
// nothing has landed, and what a surface draws is the work still going with
// one line saying what is being finished.
mend string
// life is which of the node's three lives it is in right now — the worker,
// the check, a repair round — in the words task_contract.go exports
// ([TaskPhaseWorking] and the other two). "" is a node that has not started.
//
// IT IS HELD HERE SO THAT A READER OF THE GRAPH DOES NOT HAVE TO OPEN A FILE
// FOR IT. The pulse on disk has the same word (task_beat.go) and is the right
// answer for a reader in ANOTHER process; a row this session builds for its
// own home page has the node in its hand, and reaching through the pulse's
// mutex — which a disk write is held under — from inside the graph's lock is
// exactly the thing armBeat is two steps to avoid. [Agent.enterPhase] writes
// all three of the places this word lives, so they cannot disagree.
life string
// ran is the model this node is ACTUALLY running on, when that is not the one
// its spec froze. It is written in exactly one place — the tool-use rescue in
// [Agent.newTaskAgent], which swaps an incapable model for the worker tier —
// and it exists so that swap can be told without unfreezing the spec.
//
// THE SPEC SAYS WHAT WAS ASKED FOR AND THIS SAYS WHAT ANSWERED. Writing the
// fallback back into spec.model would have been the shorter fix and the wrong
// one: the spec is the contract, frozen at admission and checkpointed, and a
// contract that edits itself is not one. Without this field the swap was
// invisible in the other direction — [TaskNode.notice] published the rejected
// id while `mend` on the same card said "model X has no tools; using Y", so
// one row disagreed with itself about what was running.
ran string
// repaired is the model a REPAIR ROUND ran on, when the cascade moved that
// round off the model the node itself is on (repair_role.go). It is "" for
// every node that was never sent back, and for every node whose repair
// floored onto its own model — an all-flash crew, or a model somebody named.
//
// IT IS THE BILL AND NOT THE ROW. [TaskNode.ran] answers "what is this node
// running on", which is a fact a surface draws; this answers "what did the
// last ten percent of this node cost extra", which is a question asked of the
// project's record afterwards, so it reaches the index
// ([TaskNode.indexEntryLocked]) and nothing a person is looking at while the
// work runs.
repaired string
// settling NAMES the resolution in flight over a landed node, in the plain
// words a second caller is told, and it is "" when nobody holds the node
// (task_audit.go's ResolveUnverified).
//
// IT COVERS ALL THREE ANSWERS, not just the re-audit it started life as. An
// accept and a refute are settles too — they finish the node and, in the
// accept's case, merge a branch — and two of them arriving in one tool batch
// (loop.go runs a batch concurrently) would both read TaskUnverified and both
// come home, which is one merge over a branch the other already deleted. So
// the state check and the settle are ONE claim, taken here (see
// [TaskNode.claimSettle]) and held until the node has resettled.
settling string
// claim is the WORK'S OWN ACCOUNT of itself — the last thing the worker said,
// before any auditor spoke — kept apart from the report because the report is
// a composed card: the audit's lead line and this underneath it.
//
// A verdict that lands on a node which has ALREADY landed (task_audit.go's
// landAudit) has to rebuild that card, and it cannot do it from the report:
// there is no way to tell the previous auditor's lines from the work's own
// once they are one string. Keeping the half that never changes is what lets