Skip to content

tui3: team delegation - #1494

Merged
AbirAbbas merged 172 commits into
devfrom
feat/team-delegation
Sep 25, 2026
Merged

AbirAbbas merged 172 commits into
devfrom
feat/team-delegation

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Stacked on #1429. Merge #1429 first. With #1429's branch deleted, this PR retargets to dev and its diff shrinks to the changes below.

Builds the delegation model around team managers, a teams page, and the layout changes that came out of trying it.

What it adds

Delegation.

  • Questions go up the tree. A member's question goes to its manager as a self-contained decision packet. A conflict between several teams goes to the lowest manager above all of them. Anything left reaches you.
  • Team settings are inherited from the team above, and the page shows where each value came from. They cover spending caps, whether questions go up, and wake rules.
  • Nested teams. A manager can start a sub-team.
  • An optional "All teams" manager sits above every team.
  • Closing a team. Wrap-up gives the team a deadline to finish before it closes. The countdown survives a restart. A closed team can be reopened, or deleted.
  • Caps. Crossing a spending cap raises one packet, even with several windows open.

Teams page.

  • A teams place: your teams as a tree on the left, and the selected team's manager chat on the right, where you can steer it.
  • A team name in any chat opens this page on that team.
  • It reads only the teams' members, so opening it went from 33 ms to 3.3 ms on the Spark test fixture. A selection made while a read is out is no longer dropped, and a manager is never opened twice.

Layout.

  • Top line: the places (home teams chats sessions spend settings) sit on the wordmark line on every page. The chat tab strip shows only inside a chat.
  • Right column: every conversation has one side column with two views, Tasks and Traffic.
    • A band at the top lists what needs you.
    • Tasks are grouped by state.
    • Each Traffic row reads from → to words.
  • Standing card: the card says what it is (a reminder, a repeating check, a watch or a rule), and each button names its outcome.
  • Completion check: it respects the answer the person gave on a card.

Over --host: the Settings Teams tab now edits the far machine.

How to try it

  1. alt+2 opens teams. Make a team, pick a manager, and give it work from the right-hand pane.
  2. In a manager chat, the right column shows Traffic. Rows read ◆ → @member, and ▸ opens the replies.
  3. Ask a manager for a reminder every few hours. The card reads Set it up · every 3 hours.

Where to start reviewing

  • Design first. docs/design/conversations-and-teams/DESIGN.md: section 8 (Delegation) and One side column in section 5.
  • Store. internal/teams: settings inheritance and provenance, decision.go for packets, spend.go for caps, and lifecycle and moves.
  • Session. internal/session: the team tools (team_decide, team_escalate, team_raise, team_close_report), team_wrapup.go, and checkpoint.go for the completion check reading card answers.
  • Interface. internal/tui3:
    • teamspage*.go and teamsopen.go: the teams page
    • topnav.go and navmore.go: the top line
    • sidecol.go and sidetraffic.go: the right column
    • standing.go: the card
  • One call that needs a reviewer's eye: a task page's child rows keep dev's command and cost lines, but in the one-line shape (no tree connectors or #ids).

Evidence

  • dev is merged in through tui3: conversation overview #1429 with a real merge commit (no rebase). The head is 6596524c2.
  • Spark, fresh clone at 6596524c2, OPENROUTER_API_KEY unset:
    • light gate green: build, go vet ./..., make fmt-check, the packed manual, codeaf-changes check, the manual tests and make test-laws
    • touched packages pass: cmd/codeaf, internal/manual, remote, session, teams, tui3, config, e2e, standing
  • GitHub CI starts once this targets dev.

🤖 Generated with Claude Code

Tile bodies hang from the top under one blank row; a waiting tile ends in
its question and an Answer button. The Spaces row is one evenly padded
segmented control ending in + New space. The foot mirrors the head, the
grid is centred between the two rules, and the head and toolbar end where
the grid ends. The toolbar says what the hovered control does. Cards and
popovers share one padding, keep their primary action last, and stay
inside the frame and off the foot. A filter or space with nothing in it
keeps the head and offers the way back. The tile look's border field is
renamed so it no longer reads as the live-state edge.
…strip has a door to the wall

A tile's top border now carries only its space dots and title. Under the
pointer or the keyboard's focus its bottom border becomes a labelled action
row (Open or Answer, Select, Spaces, Close, each with its key), in cells
fixed by the tile's width, dropping buttons from the right on narrow tiles.
The selection box is drawn on the top border only in the selection mode,
on every tile.

? opens a help sheet listing every act the page has in four groups; every
row is a press that does what its key does. The next-needing-you key moves
to n. The toolbar gains a Help button, dropped last on narrow frames.

The tab strip draws a door to the wall after the new-chat +, lit while the
wall is up, which opens and closes it and names alt+v in the hint slot.
Every word the wall, the strip chip, the dock and the help sheet draw now says team: the Teams row, + New team, the New team card, the Teams popover, Add to teams and Team settings. The files, types, helpers, hit kinds and tests are renamed to match, and /wall and its manual line mention teams.
…eams.json

teams.json is version 2: every team has a random id, a parent, a reserved manager and its colour, and fields a later build writes are kept through a load and a save. The active team, each team's place on the wall, the popovers and every pointer target name a team by id, so deleting or reordering one never retargets another. teamSetParent refuses loops and missing parents, and a deleted team hands its children to its parent. The first build's spaces.json is read once, written as teams.json with the colours it was drawn with, and renamed to spaces.json.migrated.
The new-team card still offers the shared folder's name, and otherwise a pleasant word at once, while one call on the naming role asks the agent for a one to three word name over the chosen conversations' titles. It runs off the update loop, says naming beside the field, and replaces the word only if the person has not typed; a failure or five seconds without an answer keeps the word. A team is never renamed afterwards except in its settings.
/new, which the strip's + and the start page also take, and a folder typed on home put the conversation they mint into the active team and save it, at the moment it first has its key. Switching to a conversation that already exists changes no team. A member closed before it had a name is not drawn on the strip, and saving refreshes each member's name from its tab.
A press on the chip opens a menu under it on every page, the conversations view included: each team and All with its open count, then Add or Remove this conversation for the shown team, New team, which opens the card with the conversation in front picked, and Team settings. The arrows, enter and esc drive it, and a press off it only puts it away. With teams but none shown the chip is a quiet Teams, so the first team can be chosen from the strip, and teams are read once at start so it is there from the first frame.
The new chat page covers opening the conversations view, reading a tile, the action row, selection, teams (making and naming one, colours, membership, showing one, auto-join, settings and delete), the strip's team switcher, where teams are kept, and every key. keys.md names alt+v and the view's keys, and the tasks page's handover heading answers the question about handing work to a team of workers, which the new page would otherwise take.
TestWallRenderPrintsFrame prints the new-team card while a name is asked for, and TestTeamMenuPrintsFrame prints a 120-column chat with the switcher open. The last test still named after spaces is renamed.
One call on the naming role, the same door and billing as NameTeam, with one fall-through rung at most. The model is given conversation titles and folders and the existing teams by short ref, and answers JSON of new teams and additions. The answer is validated strictly: unknown refs and repeats are dropped, names are cleaned as team names, a new team of fewer than two is dropped, and a new team named like an existing one becomes additions to it, so nothing is ever renamed or removed.
The Teams row ends in an Organize button on All (o), counting the conversations in no team from five, and reading Organized once a run found nothing and nothing changed. It opens a card of suggestions, each ticked: a folder pass that is free and exact, merged with one ProposeTeams call off the update loop with a ten second bound, the folder pass winning. Without a model answer the folder pass is shown alone and says so. Apply makes the ticked teams in one save, and the Teams row offers Undo (u) for six seconds, which restores the exact prior list. The help sheet and the key map list o and u.
Where the button is, where suggestions come from, the folders-only and nothing-to-suggest states, ticking and applying, Undo, and the o and u keys.
internal/teams holds the Team and Member types, teams.json v2 with migration from spaces.json, repairs, unknown-field round trip and atomic save, all through config.ProfilePath. Writes go through Update, a read-modify-write under an exclusive filelock with a bounded wait. Members get stable per-team handles, a team has at most one manager who is always a member, each team has an append-only Traffic log with rotation, and Digest renders a team for its manager's context.
The store moved out: team and teamMember are aliases of teams.Team and teams.Member, load, save, the tree walks, the set-aside of an unreadable file and the hue arithmetic call the package. The interface keeps the palette's reserved hues, drawing, naming and the strip. No change in what the wall or the strip does; the frame law now also names the store's disk doors.
Each edit the interface makes to the teams goes through teams.Update
(app.teamEdit): the change is made to what the window holds, then again
to the file as it is on disk under the lock, and the window adopts what
was written. A manager, handle or member another process wrote since the
window loaded is kept. Members take their tab's title on the way, so an
untitled member gets a handle once it has a title. Organize's Undo takes
back exactly what Apply did instead of saving the old list.
A team shown puts its manager first: the manager's own tab named for the
team (a diamond and the team name), or a quiet + Manager word button while
it has none, which starts a conversation in the team's folder and makes it
the manager (AddMember then SetManager). The team switcher and a tile's
Teams popover offer Make manager on a member and Remove manager on the
manager. The manager's tile is pinned first on the wall and marked.
… start

While this window holds a conversation in a team, a clock of its own reads
each such team's Traffic log off the loop (beside the door line), paging
forward from a cursor, and folds it into a cache; a first look reads the
tail, so a window opening never acts on what came before it. The same read
adopts a teams file another process changed and titles members that joined
untitled.

With the manager in front, the frame gives the right of the body to the
team's traffic, newest at the bottom, each row a door to its member; on a
narrow frame the column is a two-cell edge that lays the traffic over the
body. The composer's placeholder says to the manager.

KindStop ends the current turn of a conversation this window holds, as the
person's stop does; KindStart, in the window holding the manager, opens a
conversation in the team's folder under the manager's handle and sends the
brief marked as the manager's. Each is done once per entry id.
What a manager is, who outranks whom, the five manager verbs and the
member's post with their approval defaults, and when team messages arrive.

Claude-Session: https://claude.ai/code/session_0197RHyWZLcLUSyoJusXMoaF
A conversation finds its teams by its own conversation key (the transcript
path, cleaned and symlink-resolved, the key tui3 stores a member under) and
is a manager where Team.Manager is that key.

A manager is armed with team_status, team_read, team_send, team_stop and
team_start; a member of a managed team with team_post. They arrive through
armFamily at the first step boundary that finds the team, never in belt(),
so the fixed prefix is unchanged. Every write is one AppendTraffic entry per
the Traffic contract (note, directive, stop, start). team_start asks under
the shipped default; the rest sit on the builtin approval floor.

At every step boundary, Traffic addressed to this conversation since its
cursor is delivered as one marked session note ("◆ from manager: ...",
"from @web: ..."), once: the cursor lives in the session folder's team.json,
and a fresh conversation starts at its birth or its own start, so history is
not replayed. A manager's turn carries teams.Digest with its brief in a note
of its own, read beside the work at turn start; member states come from each
member's journal tail.

Claude-Session: https://claude.ai/code/session_0197RHyWZLcLUSyoJusXMoaF
Dev's #1424 pinned the strip's home chip at tabLead, where dev's places
bar stands; on this branch the places bar moved to the strip's lead
(placeBarLead, DESIGN.md's one top bar), and the chip stays with it. The
tests now ask for placeBarLead and the lowercase word, which is the
invariant #1424 was after: the same cells in both views.
…ation

Brings dev in through #1429. This branch's header stands: the places on
the top line, the chat strip only inside a chat, no home piece on the
strip. Dev's rules folded in: space space opens home and esc returns to
the chat (manual follows, with chats at alt+3 here), the rail's run
rows, the attribution-model setting, the plan digest on a person's turn.
The side column's Traffic marks and the teams page's marks ask the glyph
door, as the icon law requires.
… node renderer

Dev's #1410 drew a run's plan rows through the rail's family renderer
(railEntryRows, stems, folds); this branch's side column has no forest
and draws every task as one line (DESIGN.md, One side column). The
adapter keeps dev's rule, one renderer for every task, and draws each
lent node through railEntryRow, a level in under the row that carries
its run.
…int lines do

The hint law (eschint_test.go) found three lines saying esc clears
once this branch and dev met. The manual quotes follow.
…gation

# Conflicts:
#	internal/tui3/onetopbar_test.go
…e keeps its live lines

The side column draws every task as one line (DESIGN.md, One side
column), so dev's rail tests that pinned handles, connectors and a live
line under a part now pin the one line, its indent and its shape beside
a node's. A task's page has the room the column gave up and draws each
part's under-lines again (planPageLines). The head taking the pointer
drops home's project underline, and the stopped-frame guard gives the
nav room for its clock.
@santoshkumarradha

Copy link
Copy Markdown
Member Author

@AbirAbbas ready for review after #1429. Merge #1429 first with its branch deleted; this then targets dev and CI runs on it. Spark light gate and touched packages are green at 6596524 (OPENROUTER_API_KEY unset).

Base automatically changed from feat/conversation-overview to dev September 25, 2026 16:01
AbirAbbas and others added 3 commits September 25, 2026 12:32
#1429 landed on dev as one squashed commit, so dev no longer shares the
commits this branch merged from it (up to 6416a6c). This commit has
exactly the squash's tree and both as parents, so merging it tells git the
two are the same work and only the real conflicts with dev remain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merged through a commit that stands for #1429's squash with the branch this
one was built on (6416a6c) as its second parent, so only the real
conflicts show.

tabtitle: #1496 put the hovered tab's full title on the head's blank row under
the rule; on this branch a chat's head is the nav, the strip, the rule and the
blank, so that row is chatHeadRows-1 and the strip's row is tabStripRow.

notice: #1487's quiet tip reads the keys row of a put-away column, which this
branch spells through sideBackHint (`alt+l tasks`, `alt+l traffic` for a
manager) rather than the retired railBackHint.

manual: #1495's workers heading stands before this branch's rewritten roster
paragraph; team_stop keeps #1429's background-member sentence beside this
branch's new rows; the dock and wall probes are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#1485 replaced the separate plan page with the task room and retired the
work tab as a full-frame view. This branch's one-line child rows move with it:
the task room's parts are drawn through planPageLines, the side column's row
with the call and the clock and money under it. The teams pane and the nav's
head cover no longer ask about the plan page or the work tab, which are rooms
of their chat now, and the task room's subtree test reads the room rather than
the side column every chat has on this branch.

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

Copy link
Copy Markdown
Collaborator

Reviewed this and checked it by hand in the real binary. Every seat, all three managers included, was pinned to deepseek-v4-flash: 205 calls, about $0.09. All 16 of the body's claims hold:

  • Live in the binary:
    • alt+2 opens teams;
    • the top line shows on every page, with the tab strip only in a chat;
    • a team is given work from the pane;
    • a member's question reaches the manager as a decision packet, which team_decide answers;
    • nested teams and All teams work;
    • settings show · from harbor;
    • Traffic rows read ◆ → @scout, and ▸ opens the replies;
    • the card reads Set it up · every 3 hours.
  • Wrap-up: the deadline survived a window restart and an engine kill. An expired wrap-up raised exactly one report, still one after a second restart. Reopen, Undo and Delete work, and Delete keeps the members' conversations.
  • No regressions: alt+1..9, clicks on the top line and the tab strip, esc paths, two spaces going Home, and Home/Sessions enter all still work.

The review's two blockers were already fixed by you while it ran: the closed-team panic (b4ee608be) and the missing change entry (36cc6b428).

Now that #1429 (squash 5d8324a3f) and #1485 (squash 619af4850) are on dev, I'm pushing a plain merge of dev onto 36cc6b428, with no rebase:

Should-fixes for you, not blocking:

  1. teamclose.go:50: closing a sub-team whose manager alone is working skips "Wrap up first" and closes at once, while the manager keeps running. Undo works.
  2. team_cap.go:187: a cap under half a cent shows as $0.00, and Raise to $0 lifts nothing.
  3. team_cap.go:76: a spend that can't be read counts as $0, so the cap stops holding on an I/O or corrupt-file error. Wake limits still bound any loop.
  4. A wrapping-up team shows no deadline or time left anywhere.
  5. chatv3_host_teams.go never sets History, so on a plain local launch a closed team's report says is not readable over this connection.
  6. decision.go rotation drops answered packets after two rotations.

Merging once CI is green. #1436 then takes a small dev merge after this (getting-started.md, app.go).

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified by hand in the real binary (16/16 claims) and on top of dev at 619af48 (#1429 + #1485); CI green on d35411d. Merging.

@AbirAbbas
AbirAbbas merged commit e94bb4f into dev Sep 25, 2026
4 checks passed
@AbirAbbas
AbirAbbas deleted the feat/team-delegation branch September 25, 2026 17:23
AbirAbbas added a commit that referenced this pull request Sep 25, 2026
Two conflicts. getting-started.md: the crew branch's "the questions" (no
crew step) beside #1494's opt+1…opt+9. tui3/app.go's wheel: the crew
panel still takes the wheel while it is up, ahead of #1494's nav, whose
comment is kept as #1494 wrote it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AbirAbbas pushed a commit that referenced this pull request Sep 25, 2026
…1436)

A task's crew is picked per task by internal/crewroute (class, price on every connected route, quality minus lambda times cost) instead of a stored preset. /crew holds only what is allowed, what is pinned and a daily cap; --best and --cheap move one task. Old profiles migrate once. Route health, a per-seat fallback ladder, per-task and daily spend limits, /redo stronger, remote protocol 18.

Added in review: a pin keeps its thinking level, auto in the reflex or small-work row reads its default and migrates, -yes-spend passes the daily cap, the cap refusal no longer offers --cheap, zero crew money is not drawn, and the manual describes the one seats row; dev merged in over #1429, #1485, #1494 and #1511.

Co-authored-by: Santosh kumar <29346072+santoshkumarradha@users.noreply.github.com>
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