Skip to content

One task page for every task: a run's task opens the real task room - #1485

Draft
santoshkumarradha wants to merge 881 commits into
devfrom
task/one-task-page
Draft

santoshkumarradha wants to merge 881 commits into
devfrom
task/one-task-page

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

One task page for every task

In a chat, a task on the run engine opened a page of its own. #1420 restyled it to look like the task room, but it was still a second page: no transcript or work tab, no steer box, no step rows, and no tokens or model. Now every task opens the real task room, on either engine. There is one page type.

What a run's task shows in the room

The room reads the run's task through the session layer:

  • Transcript tab: the work order, then the trajectory's steps drawn as the room's own shell calls, with their output. The step in flight is the newest call. A refused action is one dim refused line. A call that did not run for any other reason has no row, but it still counts in the steps. Notes you left are drawn where corrections are drawn. Notes from a worker or the run name no author. The result comes last once the task has ended.
  • Work tab: the run's working copy, diffed against the commit it was cut from, plus the files it added. The files codeaf keeps there are left out. A copy that is gone from disk is read off its branch. This uses the new session.PlanTaskWork, built on runCopyOf and the run's notice copy.
  • Box: a message becomes a note on the store task (PlanNote). Once the store has it, the room says the worker reads a note at its next step. A refused note shows the store's own sentence on the line you typed. On a task that has ended, the room refuses the note before sending it, in the same words its box and foot already show (this task has finished), and keeps the text in the box.
  • x: raises the Stop this task? card and then stops the task through the plan's door. For the run's root, that is the run's stop.
  • Head:
    • the store's status, elapsed time and spend;
    • the model and tokens, read from the spend ledger. PlanTaskRow gains Model and Tokens, and the model shown is the one the task spent the most through.
    • Anything the ledger does not record is dropped, never shown as blank or zero.
  • Child tasks: the waits and the tasks under it are drawn under the transcript with tui3: a run's rows and its page look like the old task rows #1420's row shape. A press on any of those rows opens that task's room.
  • Following: the room follows a running task on its own 3-second beat, never on the paint clock. A task that has ended is read once.

The tasks place, the side list and the run's tab all open this room. The separate plan page is removed, along with its p key and its direct part cancel.

Tabs on every room

No room had tabs before, on either engine, so this PR adds them to every task room (not to the adaptive run's graph page):

  • transcript and work are named on the trail row beside the way back.
  • tab over an empty box switches between them, and a click on a name does the same.
  • An older-engine task's work tab lists the files it changed once it lands.

Apart from the tabs, older-engine tasks are unchanged.

Kept from #1420

  • There are still no ◑ $ rows.
  • The live step still needs Live.Step > 0 and a command.
  • A frame never reads the store.
  • Keys typed while a room opens land in its box, unsent.

Tests

  • Red first (9c0da30):
    • a new-engine task and an older-engine task open the same room type with the same tabs;
    • a message typed into a new-engine task's room reaches its store as a note.
  • internal/tui3/planroom_test.go:
    • steps become calls;
    • no live row without a step or command;
    • the refused line;
    • head model and tokens, known and unknown;
    • the work tab diff;
    • stop through the card;
    • store refusal of a note;
    • keys typed in the gap;
    • follow on the beat;
    • the live step leaving at the end;
    • a part's note reaching the part;
    • the pickup sentence;
    • a part row press.
  • Session:
    • the ledger's model and tokens on PlanTaskPage;
    • readPlanWork against a real git copy;
    • a harness file dropped whole from the patch.
  • The old page's tests are ported to the room. The tagged e2e scenario now waits for the room's tabs, only logs the note box (an ended task's box names another door), and reopens the tasks place after esc.

Tagged e2e TaskOnTheRunEngine

The room assertions pass. The scenario still fails on this branch, but every remaining failure fails the same way on clean santos/dev2:

  • the tasks place fold word → what ran under it never appears;
  • the row is already done before running can be read;
  • the landing names main instead of the run's branch.

On clean santos/dev2 the scenario also fails at the old page's own steps · $ wait.

Live check (tmux, isolated home, telemetry off, deepseek-v4-flash)

A /task on the run engine, opened from the tasks place:

  • The room shows the transcript · work tabs.
  • The head reads done 9s · 3 steps with deepseek/deepseek-v4-pro · 22.2k tok · $0.02.
  • The transcript has the brief, the steps and the report.
  • tab shows the three new files as a diff.
  • A note on the ended task is refused in the room's own words and kept in the box.

On clean santos/dev2 the same press opens the old page, which has no tabs, no model and no tokens.

Manual

Updated reading-a-task-page.md (new section on the two tabs), worker-harness.md and task-controls.md.

santoshkumarradha and others added 30 commits September 18, 2026 17:41
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ker seat)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ker seat)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…urns late (#1210)

The root's worker marks its task done in the store before it returns. When the supervisor's pass saw the store done in that gap, Start ended the run with the right outcome and an empty result. Start now reads the stored root result when the worker's return is missing. A new test holds the return back past one pass, so the gap is every run of the test.
…y, and the landing's deadline fits a real call

Deciding whether to refresh ran after every message and opened the plan store
twice each time; it now reads the rows the task sheet already carries and the
store read moves into the command. The landing waited half a second for a call
that takes two to four, so the refresh could only ever succeed in a test; it
waits six. The rail no longer draws the whole plan when its reading holds no
rows (a fallback added for a fixture), and the landing fixture sets the belt
itself instead of inheriting it from the shell that ran it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he loop and at the landing

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nced answer

The page the model read was six lines; it now says who is asking, when to open a
task, what an answer is, and what to do when the record does not hold it or the
person is steering. The rows, notes and task bodies are bounded, the page is
said once, and the answer is found by its braces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The owner, 2026-09-18: a prompt is written so it is true in any setup. Two
example lines spelled a Go file and one a Go glob; they name a path and a name
pattern instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…one next thing (#1209)

The answer section of the chat prompt is reworded, close to byte neutral, so a reply leads with the action, uses the fewest numbered steps for a sequence, says where a multi-step job stands, gives cost in minutes or hours, states an error as cause then fix, and ends on the one thing that is the person's to do next. Shaped after the i-have-adhd skill by Ayoub Ghriss (MIT); no text copied. Fixed prefix 55,253 of 55,280; lean prefix 47,042 of 47,055.
…tore from a frame

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
santoshkumarradha and others added 23 commits September 23, 2026 15:02
## Summary
- #1418 stopped refusing a deliverable path by walking up until it found a directory, declining only the filesystem root. On macOS `/home` is a symlink to `/System/Volumes/Data/home`, which is a directory, so the path that prompted the change — `/home/santosh/src/af-dev2-probe/bin/codeaf` on another host — still resolved as a place on this machine and was still refused. GLM 5.3 caught this in review of #1418.
- `placeOnThisMachine` now asks about the directory the path names and nothing above it. A real folder outside the ground is still refused by name. A path whose directory is not on this machine, including one that merely begins with a directory that is, is left alone.
- The test now uses that `/home/...` path rather than a made-up prefix, so the macOS symlink case is what it pins.

## Test plan
- [x] `go test ./internal/session/ -count=1 -timeout 180s -run 'TestAPathOnAnotherMachineDoesNotRefuseTheTask|TestATaskNamingAFolderItDoesNotStandInIsRefused|TestATaskNamingACommandInItsOwnDirectoryIsAdmitted|TestATaskWhoseAcceptanceCarriesAProseSlashIsAdmitted|TestNoRoadInTheTaskEngineHasMoreEndingsThanItsLedgerRow|TestTheRatchet'`
- [ ] `go test ./internal/session/ -count=1 -timeout 12m` (the package takes about six minutes; the targeted run above is what changed)

—
Drafted with [CodeAF](https://agentfield.ai/github/codeaf?utm_source=github&utm_medium=pull_request&utm_campaign=drafted_with) · reviewed and owned by the author
… and work/ cleanup

- Fix false-positive scope matching in contract.go and skillcatalog.go where
  path components like 'users' or 'home' matched every workspace.
- Sanitize process environment in runSkillCheck to prevent credential leakage
  to candidate check.sh scripts.
- Add deferred panic recovery in Supervisor.launch and BashWorker.Run to prevent
  process crashes and inFlight deadlocks.
- Bound SKILL.md file reads to 64KB in skills.go.
- Acquire write transaction via beginWrite() for telemetry update in
  SkillFactAccessors to prevent SQLITE_BUSY under immediate locking.
- Untrack committed scratch triage files under work/ and add work/ to .gitignore.

Assisted-by: CodeAF (local/gemini-3.8-flash-high)
Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
…s at a price (#1416)

* do, run, session: tests for the do door's contract, lockfiles, the check seat and the fallback receipt

These are written against the unfixed tree first: codeaf do committing the
person's own work on the run engine, running unbounded without --yes-spend,
dropping --db and --keep without a word; a belt landing leaving project
lockfiles behind; CODEAF_CHECK_MODEL never reaching a chat's run; and an
approved hand-off falling back to the older engine with an identical receipt.

* do, run, session: codeaf do edits in place, commits nothing, and stops at a price

The run engine is the road every codeaf do takes, and it swept the
directory's whole status into a commit on the checked-out branch, the
person's own uncommitted edits and untracked files included. It now keeps
the contract --dir states and the older road keeps: edited in place,
nothing committed. The folder is read before the run and after, and the
files the run names are the ones it changed (session.RunTreeSnapshot).

Without --yes-spend a run is bounded again: at the plan-price figure and at
what is left of today's limit, whichever is nearer, ending with exit 3.
--db is refused in words on this road, and --keep says where the run's
store is.

A belt landing no longer drops paths by a name project files share: the
.lock suffix and the bench-results folder are gone, and what the harness
writes is answered in one place (harnessWrote). CODEAF_CHECK_MODEL now
seats a chat run's checks. An approved hand-off the run engine could not
start says so on its receipt. The wave 6 change entry is rewritten to say
what is true.

* manual, do: the do pages are reachable for the questions they already answered, and the status check reads git's own columns

* session: an in-place run's snapshot asks where the repository is through the one asker

* changes: the do door's entry carries its pull request's number
* tui3: a run's rows and its page look like the old task rows

A run's parts are drawn through the node renderer (railEntryRows) by lending
each store row a node: the working spinner, the #id, the clock and price line,
and every part its own row in the old tree, never a done fold. A node row that
carries a run is drawn as the head of its family with the parts under it.

A run's page wears the task room's head: the trail with esc back, and the facts
rule led by the state's spinner. Steps are the room's shell rows, parts are the
rail's rows, and an empty live command draws no line (the stray ◑ $).

* changes: a run's rows and its page look like every other task (#1420)

* changes: name the ground-lint entry for its PR (1421)
…road says so (#1417)

* Tests first: one run per batch, no silent fallback, an honest orphan row

The tests, and only the seams they need (a slow or failing copy cut, a
waiting hook, the run's root handed to a belt worker, the name of the
run binding). No behaviour changes in this commit; every new test is
expected to fail on it.

* One run per batch: approved hand-offs join one run, never race to one store

Starting a run is now one critical section, from the look for a live run
until the run is registered, and every other hand-off waits at its door
and then joins the run as a child. A batch approved at one moment is one
run with every hand-off in it.

A run road that fails says "task N did not start: <reason>" on both the
proposal door and the typed /task, reads as a failure, and starts nothing
on the older engine. Only a missing run engine or plan place still takes
the older road.

A worker is bound to its run's root as well as its store path
(PLANDB_RUN), and the plandb CLI refuses a store at that path whose root
is another run's.

A run row's room takes a message as a note on its task's page. A run row
nothing drives answers that it can be stopped instead of "no task N in
this session", and a stop settles it.

* The failed-road test reads the success receipt's own words, not the word started

* Change entry for one run per batch

* The stop law names the start door by its new name; the row-message heading stops competing with how to stop a task

* The stop of a row nothing drives is said where stopping a run is

* The #1418 change entry carries its pull request, so the entries are well formed again

* The #1418 entry's pr line, and the remote-path test names no real person's folder

The pr line was left unstaged by the rename. The test spelled a real
checkout under /home, which exists on the machine it was measured on,
so it failed there.
… loop (#1427)

* session: a read sweep the chat will not hand off is handed off by the loop

Three prompt formulations taught the hand-off judgement and two model
families recited every one without acting on it: bounded lookups stayed
inline, and an explicit parallel instruction came back as sequential
calls. The loop is the one place the reading's cost is a fact rather
than an instruction, so the loop spends the hand-off itself.

A turn that reaches its third distinct read-only target — read, grep,
find, ls, the earlyTools set — is enumerating a set, and the remainder
goes to one quick task whose distilled answer comes back as the batch's
tool result. One file stays a step; two files stay a step; a mixed
batch, a bash call, or a write breaks the run. Every failure — refusal,
a held node, a waited-out budget, an empty answer — runs the batch
inline exactly as before, and the sweep is disabled for the rest of the
turn so a failure is paid once. The hook fires only where a hand-off
can run: conversations, with an engine linked.

Assisted-by: CodeAF (moonshotai/kimi-k3)

Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

* fix(session): cancel abandoned quick sweep, preserve rest in mixed batch fallback, and compact keys

- Fix double-execution of non-reader tools (e.g. bash, edits) during fallback when a quick task fails or times out.
- Fix event stream ordering: do not emit EventToolFinished or appendTook before confirming the hand-off succeeded.
- Fix start timer wait: arm startTimer in select so governor-held nodes time out after 30s instead of blocking for 4m.
- Clean up abandoned quick tasks with cancelTask so orphaned tasks do not consume resources.
- Compact JSON arguments in sweepKey so whitespace variations do not register as distinct targets.
- Use briefAskLimit for user question clipping in sweepBrief instead of arbitrary 400-char truncation.
- Update phase clock with tellPhase during handoff and record noteCallOutcomes.

Co-Authored-By: CodeAF <267109073+agentfield-bot@users.noreply.github.com>

---------

Co-authored-by: agentfield-bot <agentfield-bot@users.noreply.github.com>
Co-authored-by: CodeAF <267109073+agentfield-bot@users.noreply.github.com>
…nversation always moves, --max-hours ends the window (#1430)

* engine, move, max-hours: tests first — an older engine is replaced, status names it, a held journal moves, the cap ends the window

Red on the base: an older engine holding work is deferred to, --status does
not exist, the engine's held-journal refusal reaches home as a bare string so
move-it-here never asks, nothing names or stops a window that will not let go,
an engine never honours a move request, and --max-hours leaves the window open.

* engine --status, a named --stop, the older engine gives up the slot, a held conversation always moves, --max-hours ends the window

Engine. `codeaf engine --status` / `--status-all` ask the socket which engine
holds a workspace (pid, binary, build, start, windows) and stop nothing;
`--stop` / `--stop-all` name the process they stopped. An engine from an
OLDER build (by build moment, any file, or too old to answer) is replaced
by `codeaf engine --daemon` and by every window's dial, busy or not, with
one line naming it; a newer engine is joined and a tie never replaces, so
two builds converge. Stop never signals its own process.

Move. The engine's held-journal refusal reached home as a bare string, so
enter on a conversation an in-process or older window held printed the
instruction just followed and asked nothing; it now arrives as
ErrSessionLocked and home asks the holder. An engine closes a conversation
a window asked for (it had no surface to hear the request). After fifteen
seconds unanswered the card names the holder (pid, tty, build, from its
presence record) and enter offers to stop it with SIGTERM, a second time
exiting at once. The request/presence files are frozen as a protocol
between builds (session/holder.go) and pinned by byte tests.

--max-hours. The wall stopped the work and left the window waiting for a
person; it now leaves two minutes after the wall and insists thirty
seconds later.

* changes: the entry for #1430

* manual: the new sections do not pull a task question off its page, and three probes reach them

The first words of the stop section ("a window stuck") and the time-limit
section ("a test rig", "wait") moved 'is my task stuck while it waits for
its test suite' off how-tasks-run. Reworded; probes added for engine
--status, the window that will not let go and --max-hours closing the window.

* changes: the #1430 title fits on one line
… what they are for (#1396)

* skills: read Claude Code plugins, Codex system skills and linked folders; skills with memory off; the whole shelf in the catalog; /skill over the session host

* skills: gofmt

* skills: fixture names, the attachment door leaves the ledger, e2e helper name

* skills: the plugin exclusion test checks its control first

* skills: a plugin reads the skills its manifest or marketplace entry names

* changes: the skills-from-other-tools entry

* e2e, manual: the carried-skills line is 'skills ·' and folds into the work chip

* e2e: the relevance eval counts a skill the run opened, one folder per request

* tui3: the carried-skills line stays under the message, above the work chip

* tui3: the no-shelf picker test reads the reason the clipped row keeps

* e2e: the tide and ledger skills carry their answer, so the model does not go searching

* session: the catalog asks for the skill before any other tool; the eval floor sits below the spread

* skills: the attachment rides the facts photograph, and the picker asks its doors off the update loop

* tui3: the chip press test runs the clearing door it is handed

* tui3: the shelf read keeps its place in the door line
Brings #1415, #1424, #1425, #1334, #1422, #1388, #1389 and #1426 onto the
integration branch (dev at debffab).

Conflicted files and how each was settled:

- internal/tui3/homeslash.go: the opens-a-conversation fate keeps dev2's
  /skill and dev's /manual and /folder (the union). dev's /project pin and
  the removal of /image and /ask stand. The fate-table and homeTrayCommand
  comments no longer name /image or the old /folder pin.
- internal/manual/chat/home.md: dev's three rows for /attach, the commands
  that open a conversation and the ones that answer on Home, with dev2's
  /skill /skills added to the opens-a-conversation row so the table matches
  homeFate.
- internal/tui3/notice_test.go: both sides appended tests; all are kept
  (dev2's unread and retired profile-key tests, dev's ranking and rotation
  tests). newNoticeBoard's third argument is "hints enabled", so the
  hints-off test still passes false.

Not a textual conflict, but broken by the merge:

- internal/manual/chat/compacting-over-and-over.md: the probe "what
  happened to the earlier messages" reached another page once dev2's pages
  and dev's respelled heading met. The heading carries the phrase again.

Semantic collisions checked with no change needed: Escape and double-space
against dev2's task page, rail, pickers and skill chips; one /skill row and
alias; the tip table (22 rows, no priority column, the unread-keys line is
not a tip and shows with hints disabled); ui.hints stays a read key beside
dev2's task.parallel and retired-key rows; Home's working mark and the
filtered Sessions list against dev2's stopped and interrupted runs; the
attach tray carries files and skill chips side by side; the e2e word table;
system.md; icons and banned words; change entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found while merging dev into santos/dev2; every line was already stale on dev.

- attaching-files.md still said a bare /attach on Home answers "type the
  path after /attach" and that /folder is Home's browser. A bare /attach
  opens the browser for the next conversation's folder; /project pins the
  folder; /folder opens a conversation first.
- places.md said Home has no alt+<letter> keys. Home's foot draws alt+p,
  alt+e, alt+a and alt+k. The page also carried the "Where is the model
  filter in Settings" section twice, word for word; one copy is gone.
- commands.go, hometip.go and notice.go comments still placed /attach
  relative to /image, drew /ask as Home's example tip, and called /ask a
  command "on its way out".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
/skill is on Home's opens-a-conversation road, and the new conversation ran
it through a dispatcher that rewrote the box to a bare "/skill ", so
"/skills reviewer" typed on Home opened the picker with the query gone. The
dispatcher now writes the command and its words back into the box, so the
picker opens filtered on them, on Home and in a conversation alike.

commands.md's table had no /skill row; it now names /skill, its /skills
alias and what enter does on the shelf.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A task on the run engine and a task on the older engine must open the same
task room with the same two tabs, and a line typed into a run task's room
must reach its store as a note. Both fail on santos/dev2, where a run's task
opens a separate store-backed page with no tabs.
A run's task opened a page of its own that looked like the task room and
was not one. It now opens the real room, read through the run's store:

- transcript tab: the brief, the trajectory's steps as the room's own
  shell calls, the notes, and the result
- work tab: the run's working copy difference (session PlanTaskWork)
- the box leaves a note on the store task (PlanNote), and x stops it
  through the plan's door after the stop card
- the head reads the store's status, clock and spend, plus the model and
  tokens off the spend ledger, each left out when the ledger has none
- the waits and the parts are drawn under the transcript in the rail's
  row shape, and a press on one opens that task's room

Every room now carries the transcript and work tabs; an older-engine
task's work tab lists the files it changed. The tasks place, the side
list and the run's tab all open the room. The separate plan page and
its keys are gone.
The live check showed an ended task's box saying 'this task has
finished, say it to main' while enter sent a note the store then
refused. The room now refuses before sending, with the words it
already shows, and keeps the text in the box.
dev moved by one commit (f101ad2, #1440) while the first merge of the
day was being pushed; this brings it in.

Conflicted files and how each was settled:

- cmd/codeaf/chatv3_process.go: v3Process keeps dev2's catalog ownership
  (catalogs, closed, lifetime, ownCatalog, keepCatalog, takeCatalogs) and
  gains #1440's creditWatcher inside the struct. The process builds the
  skill shelf, then installs the credit watcher; neither needs the other.
  closeAll cancels processCtx and then joins the watcher.
- internal/manual/chat_test.go: dev2's six skills probes and #1440's five
  credits probes, side by side.

Broken by the merge without a textual conflict:

- internal/manual/chat/models-and-cost.md said a task on the shipped crew
  always runs on the paid worker model. While a known OpenRouter balance is
  low, an unwritten worker row now uses the free worker model; a row
  somebody chose keeps its model.

Checked with no change: the free crew covers all five tiers, and every
dev2 role (check, repair, belt worker, chat manager) reads one of them; a
402 is kept out of dev2's router retry, and the affordable-cap resend
happens once; one low-credit warning path per frame beside dev2's chips,
tip and keys rows; both launch roads wire ReadCredits once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and waited for

#1440's balance reads at launch, on a key change and on a switch to a paid
model run as Bubble Tea commands, and Bubble Tea does not wait for a
command it started. On santos/dev2 the process's closeAll cancels and joins
everything it owns (#1259, #1274), so a read could still be writing
credits.json after the process that owns the profile had closed.

tui3.Run now wraps ReadCredits in an owner that cancels every read in
flight and waits for it before Run returns, and refuses a read that starts
after close. Both local launch roads go through it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants