Skip to content

Commit 9ce657e

Browse files
Piotr Laczkowskipiotrlaczkowski
authored andcommitted
feat(teams): a team library you own, editable plans, and halves that prove themselves
Teams were something a model invented once per run and threw away. The Teams page reported on them, which is why it was empty whenever nothing was running. A team is not a per-run fact. "The backend is Go, it lives under cmd/ and internal/, go-worker writes it, `go test ./...` proves it" is true of the repository, forever — and re-deriving it from a 7–32B model each run gets it subtly wrong often enough to silently downgrade the whole run to one stream. - pkg/teams: teams are blocks (kind: team), discovered builtin → user → project. Six ship. A team carries its globs, its acceptance command, its named seats AND an open roster of as many agents and skills as its author wants. - Preselection is deterministic: query keywords, marker files and extensions, scored with no model call. The manager is the fallback, and is still asked for the CONTRACT — the one part that is about this request rather than the repo. - A contract reference that misses by a suffix resolves (`backend` → `backend-go`); an ambiguous one is refused rather than guessed. - Studio: create, edit, duplicate, delete, pin; "Try a request" shows which teams a query would get and why; Activate writes the org chart and pins it. Teams attach to a pipeline (`teams:`) and to one run (`--team`). - The approval card now edits everything the harness can apply: task description, acceptance, priority and depends_on (ordering is dependencies on a wave board), teams added from the library or removed, and the frozen contract including renaming a clause without losing its spec. - The board shows each team, its project manager, what proves its half, and cross-team waits; cards carry a team badge and a colour derived from the id. - The live view pins one line above the stream — agent, task, team, model, tokens, and a clock that keeps ticking, because on a local 30B the next log line can be four minutes out and silence reads as a hang. - Each half now RUNS its acceptance command, so green means proved. A command that cannot start is UNVERIFIED, never red: failing a team for a missing node_modules sends a corrector to rewrite code that was never at fault. Six defects the live 30B runs surfaced, each pinned by a test: - a correction ticket on the seam was stamped with whichever half appeared first on the board, then denied write access to the other half's files — it sat in ready_to_dev for the rest of the run; - a task's team stamp went stale when its files changed, with the same effect; - the splitter was never shown the ownership boundaries, so it emitted tasks spanning both halves that could never be assigned to either; - a team's reviewer and tester were editable and never consulted by routing; - duplicate tester tasks were never folded, and the dedupe ran before file reconciliation rewrote the very file sets that define duplication; - every page except / returned 404 on a browser refresh. Pages no longer strand themselves in the middle of a large screen: collections fill the viewport and turn extra width into more columns.
1 parent 0c596b0 commit 9ce657e

97 files changed

Lines changed: 11291 additions & 692 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ normalize it in `Normalize()` → handle it in `ApplyPatch()` → document it in
322322
| `pkg/evolve` | fingerprints, repair rules, bandit, reflection, regressions |
323323
| `pkg/eval`, `pkg/eval/metrics` | eval harness, per-run metrics, `Compare`, replay |
324324
| `pkg/blocks`, `pkg/pipeline`, `pkg/stacks` | YAML building blocks, phase graph, presets, **language detection** (`DetectPack` / `DetectAll` — the only implementation) |
325+
| `pkg/teams`, `pkg/squads` | `teams` is the persistent library — a team template plus the evidence that selects it, scored with **no model call**; `squads` is one run's org chart, its ownership rules and its frozen contract |
325326
| `pkg/permissions`, `pkg/hitl`, `pkg/hooks` | write/shell policy, human gates, lifecycle hooks |
326327
| `pkg/server` | Studio HTTP/SSE API, security policy, review API |
327328
| `pkg/cli` | terminal rendering: diffs, gates, REPL input, colour, width, engine-advice translation |

‎STRUCTURE.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ slmcode/
2222
│ ├── memory/ evolve/ eval/ four memory layers; repair rules + bandit; metrics
2323
│ (fed from the tool layer via workspace.ToolObserver)
2424
│ ├── blocks/ pipeline/ stacks/ YAML building blocks, phase graph, provider presets
25+
│ ├── teams/ squads/ the team library (deterministic preselection) and
26+
│ one run's org chart, ownership + frozen contract
2527
│ ├── permissions/ hitl/ hooks/ write & shell policy, human gates, lifecycle hooks
2628
│ ├── server/ Studio HTTP/SSE API + security policy
2729
│ └── cli/ terminal rendering: diffs, gates, REPL input, width

‎cmd/slmcode/cmd_block.go‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func blockList(cmd *cobra.Command, args []string) error {
154154
cli.Header("Building Blocks")
155155

156156
groups := map[string][]blocks.CatalogEntry{}
157-
kinds := []string{blocks.KindPack, blocks.KindPipeline, blocks.KindAgent, blocks.KindQuality}
157+
kinds := []string{blocks.KindPack, blocks.KindPipeline, blocks.KindAgent, blocks.KindQuality, blocks.KindTeam}
158158
for _, k := range kinds {
159159
groups[k] = nil
160160
}
@@ -191,7 +191,7 @@ func blockList(cmd *cobra.Command, args []string) error {
191191
fmt.Println(cli.Dim(" New: slmcode blocks new <kind> <id> --file <path.yaml>"))
192192
fmt.Println(cli.Dim(" Edit: slmcode blocks edit <kind> <id> --file <path.yaml>"))
193193
fmt.Println(cli.Dim(" Delete: slmcode blocks delete <kind> <id>"))
194-
fmt.Println(cli.Dim(" Show: slmcode blocks show <pipeline|agent|quality|pack> <id>"))
194+
fmt.Println(cli.Dim(" Show: slmcode blocks show <pipeline|agent|quality|pack|team> <id>"))
195195
fmt.Println(cli.Dim(" Apply: slmcode blocks apply <pack-id>"))
196196
fmt.Println(cli.Dim(" Validate: slmcode blocks validate"))
197197
return nil
@@ -210,6 +210,8 @@ func blockShow(kind, id string) error {
210210
kind = blocks.KindQuality
211211
case "pack", "packs":
212212
kind = blocks.KindPack
213+
case "team", "teams":
214+
kind = blocks.KindTeam
213215
}
214216

215217
root, err := projectRoot()
@@ -289,8 +291,31 @@ func blockShow(kind, id string) error {
289291
fmt.Print(string(data))
290292
}
291293
}
294+
case blocks.KindTeam:
295+
b, ok := reg.GetTeam(id)
296+
if !ok {
297+
return fmt.Errorf("team %q not found", id)
298+
}
299+
printBlockMeta("team:"+b.ID, b.Meta, b.Path)
300+
cli.KeyVal("owns", strings.Join(b.Spec.Owns, ", "))
301+
cli.KeyVal("acceptance", b.Spec.Acceptance)
302+
for label, id := range map[string]string{
303+
"worker": b.Spec.Worker, "reviewer": b.Spec.Reviewer,
304+
"tester": b.Spec.Tester, "manager": b.Spec.Manager,
305+
} {
306+
if id != "" {
307+
cli.KeyVal(label, id)
308+
}
309+
}
310+
if b.Path != "" {
311+
data, err := os.ReadFile(b.Path)
312+
if err == nil {
313+
fmt.Println()
314+
fmt.Print(string(data))
315+
}
316+
}
292317
default:
293-
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack)", kind)
318+
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack, team)", kind)
294319
}
295320
return nil
296321
}
@@ -451,7 +476,7 @@ func blockValidate() error {
451476
func blockNew(kind, id, filePath, name string) error {
452477
kind = blocks.NormalizeKind(kind)
453478
if kind == "" {
454-
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack)", kind)
479+
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack, team)", kind)
455480
}
456481
id = strings.ToLower(strings.TrimSpace(id))
457482
if filePath == "" && name == "" {
@@ -499,7 +524,7 @@ func blockNew(kind, id, filePath, name string) error {
499524
func blockEdit(kind, id, filePath string) error {
500525
kind = blocks.NormalizeKind(kind)
501526
if kind == "" {
502-
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack)", kind)
527+
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack, team)", kind)
503528
}
504529
id = strings.ToLower(strings.TrimSpace(id))
505530
if filePath == "" {
@@ -549,7 +574,7 @@ func blockEdit(kind, id, filePath string) error {
549574
func blockDelete(kind, id string) error {
550575
kind = blocks.NormalizeKind(kind)
551576
if kind == "" {
552-
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack)", kind)
577+
return fmt.Errorf("unknown block kind %q (use: pipeline, agent, quality, pack, team)", kind)
553578
}
554579
id = strings.ToLower(strings.TrimSpace(id))
555580

‎cmd/slmcode/cmd_core.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ func runCmd() *cobra.Command {
227227
mode, _ := cmd.Flags().GetString("mode")
228228
agent, _ := cmd.Flags().GetString("agent")
229229
pinSkills, _ := cmd.Flags().GetStringSlice("skill")
230+
pinTeams, _ := cmd.Flags().GetStringSlice("team")
230231
dynamic, _ := cmd.Flags().GetBool("dynamic")
231232
noDynamic, _ := cmd.Flags().GetBool("no-dynamic")
232233
if mode != "" {
@@ -247,6 +248,12 @@ func runCmd() *cobra.Command {
247248
query += " @skill:" + s
248249
}
249250
}
251+
// An explicit team choice is an instruction, not a hypothesis: it
252+
// is used regardless of what the query text and the workspace
253+
// would otherwise have scored.
254+
if len(pinTeams) > 0 {
255+
h.Config.Teams = append(append([]string{}, h.Config.Teams...), pinTeams...)
256+
}
250257

251258
// Pre-flight #1, and it costs nothing: resolve every human-in-the-
252259
// loop gate BEFORE the first model call. A headless run whose gate
@@ -387,6 +394,7 @@ func runCmd() *cobra.Command {
387394
cmd.Flags().Bool("dynamic", false, "run the composer specialist to assemble a task-specific pipeline (default: on)")
388395
cmd.Flags().Bool("no-dynamic", false, "disable the dynamic pipeline (use the static pipeline)")
389396
cmd.Flags().StringSlice("skill", nil, "pin/load skill by name (repeatable); also accepts @skill:name in query")
397+
cmd.Flags().StringSlice("team", nil, "run with this team from the library, whatever the query says (repeatable); see `slmcode block list --kind team`")
390398
registerDeliveryFlags(cmd)
391399
registerIsolationFlags(cmd)
392400
return cmd

‎docs/blocks.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,23 @@
1313

1414
## What are Building Blocks?
1515

16-
Blocks are **versioned, marketplace-ready YAML packages** that define reusable configurations for the SLMCode pipeline. They come in four kinds:
16+
Blocks are **versioned, marketplace-ready YAML packages** that define reusable configurations for the SLMCode pipeline. They come in five kinds:
1717

1818
| Kind | Schema | Purpose |
1919
|------|--------|---------|
2020
| `pipeline` | `PipelineBlock` | Phase graph, loop agents, insertable slots |
2121
| `agent` | `AgentBlock` | Custom specialist definition or builtin override |
2222
| `quality` | `QualityBlock` | Format/lint/test/build commands per language |
2323
| `pack` | `PackBlock` | Composes pipeline + quality + agents + skills |
24+
| `team` | `TeamBlock` | A virtual dev team: what it owns, what proves it, who staffs it — see [Teams](squads.md#the-team-library) |
2425

2526
---
2627

2728
## Discovery Order
2829

2930
Blocks are discovered in a priority chain. **First ID wins per kind:**
3031

31-
1. **Project** — `.slmcode/blocks/{pipelines,agents,quality,packs}/*.yaml`
32+
1. **Project** — `.slmcode/blocks/{pipelines,agents,quality,packs,teams}/*.yaml`
3233
2. **User** — `~/.slmcode/blocks/…` or `$XDG_CONFIG_HOME/slmcode/blocks/…`
3334
3. **Extra** — `$SLMCODE_BLOCKS` env var, walk-up `blocks/` dirs
3435
4. **Builtin** — embedded in `pkg/blocks/bundled/` (compiled into binary)
@@ -43,7 +44,7 @@ Every block YAML file shares this header:
4344

4445
```yaml
4546
api_version: blocks/v1 # required — current schema version
46-
kind: pipeline # pipeline | agent | quality | pack
47+
kind: pipeline # pipeline | agent | quality | pack | team
4748
id: my-block # lowercase kebab-case, 2-64 chars, [a-z][a-z0-9_-]+
4849
name: My Block # human-readable display name
4950
description: A reusable block

0 commit comments

Comments
 (0)