Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.

fix: network optimizations - #263

Merged
pedronauck merged 5 commits into
mainfrom
network-opts
Jul 2, 2026
Merged

pedronauck merged 5 commits into
mainfrom
network-opts

Conversation

@pedronauck

@pedronauck pedronauck commented Jul 2, 2026 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added message mentions, network channel subscriptions, and delivery-policy editing (fanout policy + optional coordinator peer).
    • Added workflows to promote thread messages into tasks and to fan out designated sibling task runs.
    • Expanded network/task views with task links, peer/coordination cost, and delivered size/token metrics; added new network response register prompt support.
  • Bug Fixes
    • Improved mentions normalization (trim, remove empties) and enforced non-negative sizing/token limits; improved canceled finish-reason categorization and structured-body truncation.
  • Chores
    • Updated API/config/web UI and broadened automated tests for the new network and task capabilities.

@pedronauck pedronauck self-assigned this Jul 2, 2026
@vercel

vercel Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agh-site Ready Ready Preview, Comment Jul 2, 2026 1:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 036c954a-1109-4dbd-a656-b09f9dd62abb

📥 Commits

Reviewing files that changed from the base of the PR and between ad93503 and 899f075.

📒 Files selected for processing (1)
  • internal/store/globaldb/global_db_network_channels.go
💤 Files with no reviewable changes (1)
  • internal/store/globaldb/global_db_network_channels.go

Walkthrough

This PR extends network messaging with mentions, delivery metadata, subscriptions, channel fanout policy, task designation fan-out, and thread-to-task promotion. It also adds the related storage, daemon, CLI, web UI, config, and schema wiring.

Changes

Network coordination and task fan-out platform

Layer / File(s) Summary
Shared contracts and types
internal/acp/types.go, internal/api/contract/*.go, internal/task/*.go, internal/store/types.go, internal/config/*
Adds and extends shared API, store, task, prompt-metadata, and config types used by the network and task flows, including validation and normalization helpers.
Mentions and delivery routing
internal/network/*, internal/store/globaldb/global_db_network_messages.go, internal/store/globaldb/global_db_network_conversations.go, internal/api/core/*, internal/cli/network.go, web/src/systems/network/components/composer/*
Propagates mentions through envelope, storage, routing, delivery, CLI send, and composer UI flows, and updates delivery accounting with prompt size, token estimates, and peer size metrics.
Delivery subscriptions and channel policy
internal/store/globaldb/global_db_network_preferences.go, internal/network/router.go, internal/network/manager.go, internal/api/core/network_conversations.go, internal/cli/network.go, web/src/systems/network/components/thread-overlay/*
Adds subscription persistence and routing controls, plus channel fanout policy fields, update flows, and the corresponding API, CLI, web, and router wiring.
Task designation and fan-out
internal/task/*, internal/api/core/tasks.go, internal/store/globaldb/global_db_task*.go, internal/cli/task.go, web/src/hooks/routes/use-task-detail-orchestration-tab.ts, web/src/routes/_app/tasks.$id.tsx
Adds designation-group-aware task runs, fan-out creation, rollup persistence, and the surrounding task API, storage, CLI, and UI support.
Thread promotion and status observer
internal/api/core/network_conversations.go, internal/daemon/network_task_status_observer.go, internal/daemon/task_event_bridge_notifier.go, internal/daemon/task_runtime.go, internal/daemon/native_tools.go, web/src/systems/network/components/thread-overlay/*
Adds thread-to-task promotion, origin linkage, and the daemon observer/runtime path that emits task status and fanout rollup updates back to the originating thread.
Config, prompt sections, and tool registry wiring
internal/config/*, internal/daemon/*prompt*, internal/tools/builtin/*, web/src/routes/_app/settings/*, web/src/hooks/routes/*
Adds network runtime config fields, prompt startup/register wiring, native tool schema/descriptor updates, and the corresponding settings UI surface.
Storage and schema migrations
internal/store/globaldb/*
Extends global schema, migrations, and globaldb helpers for mentions, subscriptions, fanout policy, peer stats, task designation rollups, and promotion origins.
CLI and web UI
internal/cli/*, web/src/*
Extends network/task command surfaces, adapters, hooks, and settings/thread/task UI for the new behaviors.

Estimated code review effort: 5 (Critical) | ~180 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NetworkConversationsAPI
  participant TaskManager
  participant GlobalDB
  participant NetworkTaskStatusObserver

  Client->>NetworkConversationsAPI: POST promote-task
  NetworkConversationsAPI->>TaskManager: create task from thread message
  TaskManager->>GlobalDB: persist task
  NetworkConversationsAPI->>GlobalDB: PutNetworkTaskThreadOrigin
  NetworkConversationsAPI-->>Client: task + origin

  TaskManager->>NetworkTaskStatusObserver: task event
  NetworkTaskStatusObserver->>GlobalDB: ListNetworkTaskThreadOrigins
  NetworkTaskStatusObserver->>GlobalDB: PutTaskDesignationRollup
  NetworkTaskStatusObserver->>NetworkConversationsAPI: send thread status / rollup update
Loading
sequenceDiagram
  participant DeliveryCoordinator
  participant SubscriptionStore
  participant GuidanceStore
  participant Prompter

  DeliveryCoordinator->>SubscriptionStore: resolve delivery mode
  alt digest mode
    DeliveryCoordinator->>DeliveryCoordinator: collect digest batch
    DeliveryCoordinator->>Prompter: render digest prompt
  else full mode
    DeliveryCoordinator->>Prompter: render single prompt
  end
  DeliveryCoordinator->>GuidanceStore: markGuidanceDelivered
  DeliveryCoordinator->>DeliveryCoordinator: allocate prompt cost
Loading

Possibly related PRs

  • compozy/agh#15: Introduces the base network stack that this PR extends with mentions, subscriptions, fanout, and delivery-routing behavior.
  • compozy/agh#19: Establishes the task API surface that this PR extends with designation fan-out and rollup persistence.
  • compozy/agh#105: Modifies PromptNetworkMeta, which this PR further extends with mentions and prompt-cost fields.

Suggested labels: enhancement, area/network, area/tasks, area/storage, area/cli, area/web

Suggested reviewers: compozy-core-maintainers

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is broad, but it is clearly related to the PR’s network-focused changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch network-opts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor found no issues. 🎉

Reviewed by React Doctor for commit 899f075.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
internal/daemon/composed_assembler_test.go (1)

495-526: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Wrap test body in t.Run("Should ...").

This test doesn't use the t.Run("Should ...") subtest pattern required for Go tests in this repo.

As per path instructions: "MUST use t.Run("Should...") pattern for ALL test cases".

♻️ Proposed refactor
 func TestComposedAssemblerAssembleStartupLoadsNetworkResponseRegisterSection(t *testing.T) {
 	t.Parallel()
-
-	resolver := NewHarnessContextResolver(HarnessRuntimeSignals{})
-	assembler := NewComposedAssembler(
-		WithSectionSelector(NewSectionSelector(resolver, nil)),
-		WithPromptSectionDescriptors(defaultStartupPromptSectionDescriptors(nil, nil, nil)...),
-	)
-
-	got := assembleStartupPrompt(
-		t,
-		assembler,
-		session.StartupPromptContext{
-			SessionType: session.SessionTypeUser,
-			Channel:     "builders",
-		},
-		testPromptAgent("Base prompt."),
-		t.TempDir(),
-	)
-
-	networkSkill, err := skillbundled.LoadResource(bundledAghSkillName, bundledNetworkReference)
-	if err != nil {
-		t.Fatalf("LoadResource(%q, %q) error = %v", bundledAghSkillName, bundledNetworkReference, err)
-	}
-	if !strings.Contains(got, "# AGH Network Response Register") ||
-		!strings.Contains(got, "Threads decide and discuss; actionable work is promoted to tasks") {
-		t.Fatalf("AssembleStartup() = %q, want compact network response register", got)
-	}
-	if strings.Contains(got, strings.TrimSpace(networkSkill)) {
-		t.Fatalf("AssembleStartup() loaded full network reference instead of compact register: %q", got)
-	}
+	t.Run("Should render compact network response register instead of full skill", func(t *testing.T) {
+		t.Parallel()
+		resolver := NewHarnessContextResolver(HarnessRuntimeSignals{})
+		assembler := NewComposedAssembler(
+			WithSectionSelector(NewSectionSelector(resolver, nil)),
+			WithPromptSectionDescriptors(defaultStartupPromptSectionDescriptors(nil, nil, nil)...),
+		)
+
+		got := assembleStartupPrompt(
+			t,
+			assembler,
+			session.StartupPromptContext{
+				SessionType: session.SessionTypeUser,
+				Channel:     "builders",
+			},
+			testPromptAgent("Base prompt."),
+			t.TempDir(),
+		)
+
+		networkSkill, err := skillbundled.LoadResource(bundledAghSkillName, bundledNetworkReference)
+		if err != nil {
+			t.Fatalf("LoadResource(%q, %q) error = %v", bundledAghSkillName, bundledNetworkReference, err)
+		}
+		if !strings.Contains(got, "# AGH Network Response Register") ||
+			!strings.Contains(got, "Threads decide and discuss; actionable work is promoted to tasks") {
+			t.Fatalf("AssembleStartup() = %q, want compact network response register", got)
+		}
+		if strings.Contains(got, strings.TrimSpace(networkSkill)) {
+			t.Fatalf("AssembleStartup() loaded full network reference instead of compact register: %q", got)
+		}
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/daemon/composed_assembler_test.go` around lines 495 - 526, Wrap the
body of TestComposedAssemblerAssembleStartupLoadsNetworkResponseRegisterSection
in a t.Run("Should ...") subtest to match the repo’s required Go test pattern.
Keep the existing assertions and setup inside the new subtest, and preserve the
outer t.Parallel() behavior where appropriate by using the test name as the
single subtest for the assembled startup prompt check.

Source: Path instructions

internal/api/core/network_details.go (1)

1625-1649: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

cloneNetworkMessageEntry drops the Mentions field.

The clone was updated to copy SizeBytes (line 1646) but the Mentions field added elsewhere in this PR is missing from the returned struct. This function backs summarizeNetworkMessageHistory's conversation/presence-episode entries (lines 1442, 1455), so any message rendered through those paths will silently lose its mentions even though message.Mentions is correctly used for participant tallying (which reads the original, uncloned entry).

🐛 Proposed fix
 func cloneNetworkMessageEntry(entry store.NetworkMessageEntry) store.NetworkMessageEntry {
 	return store.NetworkMessageEntry{
 		MessageID:   strings.TrimSpace(entry.MessageID),
 		WorkspaceID: strings.TrimSpace(entry.WorkspaceID),
 		SessionID:   strings.TrimSpace(entry.SessionID),
 		Channel:     strings.TrimSpace(entry.Channel),
 		Surface:     strings.TrimSpace(entry.Surface),
 		ThreadID:    strings.TrimSpace(entry.ThreadID),
 		DirectID:    strings.TrimSpace(entry.DirectID),
 		Direction:   strings.TrimSpace(entry.Direction),
 		PeerFrom:    strings.TrimSpace(entry.PeerFrom),
 		PeerTo:      strings.TrimSpace(entry.PeerTo),
+		Mentions:    cloneTrimmedStrings(entry.Mentions),
 		Kind:        strings.TrimSpace(entry.Kind),
 		WorkID:      strings.TrimSpace(entry.WorkID),
 		ReplyTo:     strings.TrimSpace(entry.ReplyTo),
 		TraceID:     strings.TrimSpace(entry.TraceID),
 		CausationID: strings.TrimSpace(entry.CausationID),
 		Intent:      strings.TrimSpace(entry.Intent),
 		Text:        entry.Text,
 		PreviewText: strings.TrimSpace(entry.PreviewText),
 		Body:        cloneRawMessage(entry.Body),
 		SizeBytes:   entry.SizeBytes,
 		Timestamp:   entry.Timestamp.UTC(),
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/network_details.go` around lines 1625 - 1649,
cloneNetworkMessageEntry is missing the new Mentions field, so cloned network
messages lose mention data even though the original entry has it. Update the
returned store.NetworkMessageEntry in cloneNetworkMessageEntry to copy Mentions
alongside the existing fields, preserving the value used by
summarizeNetworkMessageHistory for conversation and presence-episode entries.
Keep the clone behavior consistent with the rest of the struct by copying the
field directly from entry.
internal/store/types.go (1)

827-841: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require a coordinator peer for coordinator fanout.

Line 834 accepts fanout_policy="coordinator" even when CoordinatorPeerID is blank, creating a channel policy that has no coordinator to activate.

Proposed fix
-	if err := ValidateNetworkFanoutPolicy(e.FanoutPolicy); err != nil {
+	policy := NormalizeNetworkFanoutPolicy(e.FanoutPolicy)
+	if err := ValidateNetworkFanoutPolicy(policy); err != nil {
 		return err
 	}
-	if strings.TrimSpace(e.CoordinatorPeerID) != "" {
-		if err := validateNetworkPeerID(e.CoordinatorPeerID, "network channel coordinator_peer_id"); err != nil {
+	coordinatorPeerID := strings.TrimSpace(e.CoordinatorPeerID)
+	if policy == NetworkFanoutPolicyCoordinator && coordinatorPeerID == "" {
+		return fmt.Errorf("store: network channel coordinator_peer_id is required for coordinator fanout")
+	}
+	if coordinatorPeerID != "" {
+		if err := validateNetworkPeerID(coordinatorPeerID, "network channel coordinator_peer_id"); err != nil {
 			return err
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/types.go` around lines 827 - 841,
`NetworkChannelEntry.Validate` currently allows `fanout_policy="coordinator"`
without any `CoordinatorPeerID`, which leaves coordinator fanout without an
active coordinator. Update the validation in `NetworkChannelEntry.Validate` to
require a non-blank `CoordinatorPeerID` whenever
`ValidateNetworkFanoutPolicy(e.FanoutPolicy)` resolves to coordinator; keep the
existing `validateNetworkPeerID` check for non-empty IDs and return a clear
validation error when the coordinator peer is missing.
internal/store/globaldb/global_db.go (1)

1489-1509: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don’t change historical migration replay.

These changes alter migration 25/26 rebuild behavior by adding new columns/schema through old helpers. Since migrations 43–47 already append the new schema work, keep historical migration helpers byte-for-byte compatible with their original behavior and let only tail migrations add fanout_policy, coordinator_peer_id, and mentions_json. As per coding guidelines, internal/store/**/*.go: “SQLite migration registries are append-only. Never insert, reorder, rename, renumber, or change an existing migration identity after it may have been applied.”

Also applies to: 1566-1627

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/globaldb/global_db.go` around lines 1489 - 1509, The
historical migration helper is being changed to include newer schema fields,
which breaks replay compatibility for already-applied migrations. Revert the
changes in the migration 25/26 rebuild path in globaldb helpers so they remain
byte-for-byte consistent with their original behavior, and keep the new columns
like fanout_policy, coordinator_peer_id, and mentions_json confined to the later
tail migration code paths. Use the existing migration registry helpers in
internal/store/globaldb/global_db.go and the later migration blocks as the only
place where the new schema should be appended.

Source: Coding guidelines

internal/network/delivery.go (1)

579-610: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Move guidance persistence outside c.mu.

Line 594 performs store I/O while the delivery coordinator mutex is held, so a slow SQLite write can block unrelated queue operations, waiters, and delivery progress.

Proposed fix
-	c.mu.Lock()
-	defer c.mu.Unlock()
+	c.mu.Lock()
 	queue := c.queues[target]
 	if queue == nil || queue.token != item.SessionToken {
+		c.mu.Unlock()
 		return
 	}
 
 	state := c.guidance[target]
 	state.replyDelivered = true
 	state.loaded = true
 	if lifecycleWorkID(item.Envelope) != "" {
 		state.protocolDelivered = true
 	}
 	c.guidance[target] = state
-	if c.guidanceStore != nil {
-		if err := c.guidanceStore.PutNetworkDeliveryGuidanceState(
+	guidanceStore := c.guidanceStore
+	persisted := store.NetworkDeliveryGuidanceState{
+		SessionID:                 target,
+		ReplyGuidanceDelivered:    state.replyDelivered,
+		ProtocolGuidanceDelivered: state.protocolDelivered,
+	}
+	c.mu.Unlock()
+
+	if guidanceStore != nil {
+		if err := guidanceStore.PutNetworkDeliveryGuidanceState(
 			c.lifecycleCtx,
-			store.NetworkDeliveryGuidanceState{
-				SessionID:                 target,
-				ReplyGuidanceDelivered:    state.replyDelivered,
-				ProtocolGuidanceDelivered: state.protocolDelivered,
-			},
+			persisted,
 		); err != nil && c.logger != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/network/delivery.go` around lines 579 - 610, The guidance state
persistence in delivery coordination is happening while c.mu is held, which can
block unrelated queue and waiter activity during a slow store write. Refactor
the logic in the delivery path around c.guidance,
c.guidanceStore.PutNetworkDeliveryGuidanceState, and the c.logger.Warn handling
so the mutex only protects in-memory state updates, then perform the persistence
after unlocking using a copied snapshot of the guidance state and target/session
values.
🟡 Minor comments (9)
web/src/routes/_app/settings/network.tsx-383-397 (1)

383-397: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add inline validation for Digest flush interval web/src/routes/_app/settings/network.tsx:383-397

This is the only new delivery field that skips the validation/error pattern used by the sibling inputs. It accepts any string until save, so invalid durations surface only as a generic save failure instead of inline feedback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/routes/_app/settings/network.tsx` around lines 383 - 397, The Digest
flush interval input in the network settings form is missing the same inline
validation/error handling used by the sibling delivery fields. Update the Input
and its surrounding state logic in the settings page component to validate the
value as the user types, surface the field-specific error inline, and keep the
existing save flow unchanged; use the same pattern already implemented for the
other delivery interval inputs in this form.

Source: Coding guidelines

internal/tools/builtin/tasks.go-539-551 (1)

539-551: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject empty fan-out designation payloads at the tool schema boundary.

designations: [] and {"brief": ""} currently pass this schema, so tool callers can make avoidable mutating requests that only fail deeper in the task service.

Proposed schema tightening
 		"designations":{
 			"type":"array",
+			"minItems":1,
 			"items":{
 				"type":"object",
 				"required":["brief"],
 				"properties":{
-					"brief":{"type":"string"},
+					"brief":{"type":"string","minLength":1},
 					"metadata":{},
 					"idempotency_key":{"type":"string"}
 				},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tools/builtin/tasks.go` around lines 539 - 551, The task tool schema
for designations is too permissive because the `designations` array in the
schema built in `internal/tools/builtin/tasks.go` allows empty fan-out payloads
and empty `brief` values. Tighten the `designations` item schema used by the
task tool so callers cannot submit `designations: []` or a designation with an
empty `brief`, and keep the change localized to the schema definition in the
task tool setup around the `designations` property.
web/src/systems/network/components/shell/channel-header.tsx-206-220 (1)

206-220: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unhandled rejection propagates from updateChannel.mutateAsync.

mutateAsync rejects on failure; this onSubmit doesn't catch it, so the rejection bubbles into ChannelPolicyDialog's handleSubmit. See the companion comment in channel-policy-dialog.tsx for the root-cause fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/network/components/shell/channel-header.tsx` around lines 206
- 220, The ChannelPolicyDialog onSubmit handler is letting the rejection from
updateChannel.mutateAsync escape, which bubbles into the dialog submit flow.
Update the onSubmit callback in channel-header.tsx to handle the async failure
explicitly by catching the mutation error (or otherwise swallowing/forwarding it
safely) around updateChannel.mutateAsync, so ChannelPolicyDialog’s handleSubmit
does not receive an unhandled rejection.
web/src/systems/network/components/shell/channel-policy-dialog.tsx-111-120 (1)

111-120: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Wrap onSubmit in try/catch to avoid an unhandled rejection on failure.

onSubmit ultimately calls mutateAsync, which rejects on error. Since handleSubmit isn't awaited by React's form onSubmit, a failed submission becomes an unhandled promise rejection, and onOpenChange(false) is skipped by the throw rather than by an explicit decision.

🛡️ Proposed fix
   const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
     event.preventDefault();
     const payload: NetworkChannelUpdateRequest = {
       purpose: purpose.trim(),
       fanout_policy: policy,
       coordinator_peer_id: coordinatorPeerId.trim(),
     };
-    await onSubmit(payload);
-    onOpenChange(false);
+    try {
+      await onSubmit(payload);
+      onOpenChange(false);
+    } catch {
+      // Failure is surfaced via toast in the mutation's onError handler;
+      // keep the dialog open so the user can retry.
+    }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/network/components/shell/channel-policy-dialog.tsx` around
lines 111 - 120, Wrap the ChannelPolicyDialog submit flow in a try/catch so a
failed async submit does not become an unhandled rejection. Update handleSubmit
to call onSubmit(payload) inside try/catch, and only call onOpenChange(false)
after a successful submit; in the catch, handle or surface the error explicitly
and avoid closing the dialog on failure. Use the handleSubmit and onSubmit
symbols in channel-policy-dialog.tsx to locate the change.
web/src/systems/network/components/thread-overlay/thread-subscription-control.tsx-56-70 (1)

56-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Error state not surfaced — dropdown falls back to "Default" on fetch failure.

When subscriptions query fails, current becomes null and the label shows "Default" even if a subscription exists, while the trigger stays interactive. A user could then unintentionally reset/overwrite an existing subscription during a transient fetch error.

🛠️ Suggested fix
-  const isBusy = upsert.isPending || remove.isPending || subscriptions.isFetching;
+  const isBusy = upsert.isPending || remove.isPending || subscriptions.isFetching;
+  const hasError = subscriptions.isError;
   const label = modeLabel(current?.mode);

and disable the trigger / show an indicator when hasError is true instead of silently defaulting to "Default".

As per path instructions, "Handle all states — loading, error, and empty (never assume data exists)".

Also applies to: 93-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/src/systems/network/components/thread-overlay/thread-subscription-control.tsx`
around lines 56 - 70, The thread subscription dropdown is treating a failed
`useQuery(networkSubscriptionsOptions(...))` the same as an empty result, so
`current` becomes null and the control silently falls back to “Default”. Update
`thread-subscription-control.tsx` in the `subscriptions`/`current`/`label` flow
to explicitly detect the query error state from `useQuery`, surface an error
indicator or message, and disable the trigger while `hasError` is true so the
user cannot overwrite an existing subscription during a transient failure. Also
handle loading and empty states separately without assuming `subscriptions.data`
exists, and apply the same state handling to the related trigger/overlay logic
around the referenced subscription controls.

Source: Path instructions

internal/api/spec/spec.go-1630-1649 (1)

1630-1649: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add the limit query parameter to listNetworkSubscriptions.
parsePositiveIntQuery reads limit, so this endpoint’s spec is missing a supported pagination/filter parameter and generated clients won’t expose it.

Suggested fix
 		Parameters: []ParameterSpec{
 			pathParam("workspace_id", "Workspace id"),
 			pathParam("channel", "Network channel"),
 			queryParam("peer_id", "Filter subscriptions by peer id", false),
 			queryParam("thread_id", "Filter subscriptions by thread id", false),
+			intQueryParam("limit", "Maximum number of subscriptions to return"),
 		},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/spec/spec.go` around lines 1630 - 1649, The
listNetworkSubscriptions endpoint spec is missing the supported limit query
parameter, so add it alongside peer_id and thread_id in the Parameters list for
the network subscriptions operation. Use the existing queryParam helper in
spec.go so generated clients expose the same pagination/filter input that
parsePositiveIntQuery already accepts, keeping the spec aligned with the handler
and contract.
internal/api/core/network_conversations.go-891-899 (1)

891-899: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Omit empty coordination-cost payloads.

CoordinationCost is optional, but this converter always returns a non-nil pointer; zero-cost threads serialize as coordination_cost: {} instead of omitting the field.

Proposed fix
 func NetworkCoordinationCostPayloadFromStore(
 	thread store.NetworkThreadSummary,
 ) *contract.NetworkCoordinationCostPayload {
+	if thread.DeliveredCount == 0 &&
+		thread.PromptSizeBytes == 0 &&
+		thread.EstimatedPromptTokens == 0 {
+		return nil
+	}
 	return &contract.NetworkCoordinationCostPayload{
 		DeliveredCount:        thread.DeliveredCount,
 		PromptSizeBytes:       thread.PromptSizeBytes,
 		EstimatedPromptTokens: thread.EstimatedPromptTokens,
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/network_conversations.go` around lines 891 - 899,
NetworkCoordinationCostPayloadFromStore always returns a non-nil payload, which
causes empty coordination_cost values to be serialized instead of omitted.
Update NetworkCoordinationCostPayloadFromStore to detect an all-zero
NetworkThreadSummary cost and return nil in that case, and only build a
contract.NetworkCoordinationCostPayload when at least one of DeliveredCount,
PromptSizeBytes, or EstimatedPromptTokens is set.
internal/api/core/network_details.go-130-133 (1)

130-133: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align fanout validation between create and update.

  • createNetworkChannelFanoutFields routes store.ValidateNetworkFanoutPolicy failures through StatusForNetworkError, but that error is not mapped there, so invalid fanout policy can return 500 on create while update returns 400.
  • NetworkChannelEntry.Validate() only checks that CoordinatorPeerID is a valid peer ID; add the policy-specific coordinator requirement/forbidden rule in one place if that coupling is intended.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/network_details.go` around lines 130 - 133, The fanout
validation path in create is inconsistent with update because
`createNetworkChannelFanoutFields` and `NetworkChannelEntry.Validate` do not
enforce the same policy-specific rules. Update the fanout handling around
`createNetworkChannelFanoutFields` so invalid
`store.ValidateNetworkFanoutPolicy` input is mapped to the same client-facing
error behavior as update, and add the coordinator requirement/forbidden check in
`NetworkChannelEntry.Validate` if that policy coupling belongs there. Keep the
logic aligned with the existing `createNetworkChannelFanoutFields`,
`NetworkChannelEntry.Validate`, and `StatusForNetworkError` flow so create and
update reject the same invalid fanout requests.
internal/store/globaldb/global_db_network_conversation_repository_test.go-243-337 (1)

243-337: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required test-placement metadata.

Before this new case, name the invariant, owning layer, and canonical suite so the test’s placement remains intentional. As per path instructions, **/*_test.go: “Test placement is mandatory before test creation. Before adding, moving, or expanding any test, name the invariant, owning layer, and canonical suite.”

Proposed addition
+	// Invariant: delivered prompt-cost updates aggregate by thread peer.
+	// Owning layer: globaldb network conversation repository.
+	// Canonical suite: TestGlobalDBWriteConversationMessageThreadSummaries.
 	t.Run("Should aggregate delivered prompt cost by thread peer", func(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/globaldb/global_db_network_conversation_repository_test.go`
around lines 243 - 337, The new test case in globalDB network conversation
repository needs the required test-placement metadata before the t.Run block.
Add the invariant, owning layer, and canonical suite near the existing test
around ListNetworkThreadPeerTokenStats and GetThread so the placement is
intentional and traceable. Use the identifiers openTestGlobalDB,
UpdateNetworkThreadPeerTokenStats, ListNetworkThreadPeerTokenStats, and
GetThread to anchor the metadata to this test scope.

Source: Path instructions

🧹 Nitpick comments (11)
internal/config/config.go (1)

459-472: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider upper bounds for the new byte-size fields.

MaxPayload validates both a lower and upper bound (<= 0 and > 1<<31-1), but ResponseGuidanceMaxBytes and DeliveryStructuredBodyMaxBytes only check positivity. Not blocking, but for consistency with the existing pattern you may want to cap these too.

Also applies to: 1742-1762

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/config.go` around lines 459 - 472, Add upper-bound validation
for the new byte-size settings in NetworkConfig alongside the existing
MaxPayload checks. Update the validation logic that handles
ResponseGuidanceMaxBytes and DeliveryStructuredBodyMaxBytes so they also reject
values above the same practical maximum used for other size fields, matching the
pattern in the config validation code and any related defaults/tests around
those symbols.
web/src/hooks/routes/use-task-detail-orchestration-tab.ts (1)

117-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Toast message doesn't pluralize correctly for a single run.

${result.runs.length} designated runs queued. always uses plural "runs", producing "1 designated runs queued." when only one designation is fanned out.

✏️ Proposed fix
-        toast.success(`${result.runs.length} designated runs queued.`);
+        const count = result.runs.length;
+        toast.success(`${count} designated run${count === 1 ? "" : "s"} queued.`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/hooks/routes/use-task-detail-orchestration-tab.ts` around lines 117 -
133, The success toast in handleFanOutRuns always uses the plural “runs,” so
update the message to handle singular vs plural based on result.runs.length.
Keep the logic in use-task-detail-orchestration-tab’s handleFanOutRuns callback,
and build the toast text from the count so it says “1 designated run queued.”
for one item and “N designated runs queued.” otherwise.
internal/api/core/tasks.go (1)

2119-2134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reusing tool-invoke-status constants for an unrelated rollup schema.

toolInvokeStatusCompleted/toolInvokeStatusCanceled (defined in internal/api/core/tools.go for tool invocation status) are used as JSON map keys for the task designation rollup. It works today because the string values happen to coincide with "completed"/"canceled", but it couples two unrelated concerns and will silently drift if either constant's value ever changes for its original purpose. Define dedicated constants (or use literal strings, consistent with "failed", "running", etc. in the same map) for the rollup schema.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/tasks.go` around lines 2119 - 2134, The task designation
rollup in the JSON map is reusing tool-invoke status constants for unrelated
keys, which tightly couples two separate schemas. Update the map construction in
the rollup logic to use dedicated rollup key constants or plain literal strings
for the completed and canceled fields, matching the other schema keys already
present in this payload. Keep the change localized around the JSON encoding
block so it no longer depends on toolInvokeStatusCompleted or
toolInvokeStatusCanceled from tools.go.
internal/observe/tasks.go (1)

1249-1268: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate designation-population logic across observe/tasks.go and task/live.go.

taskInboxRunSummary here and runSummaryFromRun in internal/task/live.go both build the RunSummary and then separately overwrite DesignationGroupID/Designation via taskpkg.DesignationFromRun. Consider adding a small helper on taskpkg.RunSummary (or a package-level function) that both call sites can share, so the derivation logic lives in one place.

♻️ Proposed refactor (sketch)
+// in internal/task/designation.go
+func ApplyDesignation(summary *RunSummary, run Run) {
+	if designation, ok := DesignationFromRun(run); ok {
+		summary.DesignationGroupID = designation.GroupID
+		summary.Designation = designation.Summary()
+	}
+}

Then both taskInboxRunSummary and runSummaryFromRun call taskpkg.ApplyDesignation(summary, run) instead of repeating the conditional inline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/observe/tasks.go` around lines 1249 - 1268, The
designation-population logic is duplicated between taskInboxRunSummary and
runSummaryFromRun, so extract the conditional taskpkg.DesignationFromRun
handling into a shared helper on taskpkg.RunSummary or a package-level function.
Update both call sites to build the base RunSummary and then invoke the shared
helper (for example, ApplyDesignation) to set DesignationGroupID and Designation
consistently in one place.
internal/config/task_orchestration.go (1)

90-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the 5 fan-out cap into a shared constant.

The designated-run ceiling (5) appears as the default, the validation upper bound here, and again as a hardcoded fallback in internal/api/core/tasks.go and internal/daemon/native_tools.go (per the provided downstream context). Centralizing it avoids drift if the cap ever changes.

♻️ Proposed refactor
+const DefaultDesignatedRunMax = 5
+
 func DefaultTaskConfig() TaskConfig {
 	return TaskConfig{
 		Orchestration: TaskOrchestrationConfig{
 			...
-			DesignatedRunMax:          5,
+			DesignatedRunMax:          DefaultDesignatedRunMax,
 			...
 
 func (c TaskOrchestrationConfig) Validate(path string) error {
 	...
-	if c.DesignatedRunMax <= 0 || c.DesignatedRunMax > 5 {
+	if c.DesignatedRunMax <= 0 || c.DesignatedRunMax > DefaultDesignatedRunMax {
 		return fmt.Errorf("%s.designated_run_max must be between 1 and 5: %d", path, c.DesignatedRunMax)
 	}

Also applies to: 173-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/task_orchestration.go` at line 90, The designated-run fan-out
cap is hardcoded as 5 in multiple places, so extract it into a shared constant
and use it for the default, validation upper bound, and fallback paths. Update
the configuration logic in task orchestration (including the `DesignatedRunMax`
default/validation) and replace the duplicated fallback values in
`internal/api/core/tasks.go` and `internal/daemon/native_tools.go` with the
shared symbol so the cap stays consistent.
internal/daemon/task_role_runtime.go (1)

590-618: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extra blank line when a designation is present.

taskRoleDesignationOverlay returns a string with a trailing "\n", and the enclosing template also places %s on its own template line ending in \n, so a present designation renders with a blank line after it in the prompt (Coordination channel: block). Not functionally wrong, but a minor prompt-formatting inconsistency.

✏️ Optional cleanup
-	return "Designated fan-out assignment: " + strings.Join(parts, ", ") + ".\n"
+	return "Designated fan-out assignment: " + strings.Join(parts, ", ") + "."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/daemon/task_role_runtime.go` around lines 590 - 618, The prompt
formatting in taskRoleDesignationOverlay is adding an extra blank line when a
designation exists because the helper returns a trailing newline and the
surrounding fmt.Sprintf template already places the designation on its own line.
Update taskRoleDesignationOverlay (and, if needed, the fmt.Sprintf block that
uses designation) so the designation text is emitted without a double line break
while preserving the rest of the taskRoleRuntime prompt formatting.
internal/api/core/network_test.go (1)

1424-1509: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New subtests skip t.Parallel().

Both new subtests ("Should update network channel policy", "Should upsert and list network subscriptions...") omit t.Parallel() inside the t.Run closure.

As per coding guidelines, **/*_test.go: "Use t.Run("Should ...") subtests in Go tests, with t.Parallel by default (opt-out with t.Setenv)".

♻️ Suggested fix
 	t.Run("Should update network channel policy", func(t *testing.T) {
+		t.Parallel()
 		updateResp := performRequest(
 	t.Run("Should upsert and list network subscriptions with default channel metadata", func(t *testing.T) {
+		t.Parallel()
 		upsertResp := performRequest(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/network_test.go` around lines 1424 - 1509, The new network
API subtests are missing the default parallelization used in Go tests. Add
t.Parallel() at the start of each t.Run closure for the new cases in
network_test.go, specifically the subtests around updating channel policy and
upserting/listing subscriptions, unless they must opt out because they use
t.Setenv or other shared state.

Source: Coding guidelines

web/src/systems/network/components/shell/__tests__/channel-policy-dialog.test.tsx (1)

39-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering fanout-policy/coordinator selection changes.

Only the purpose-edit path is exercised; the new fanout policy and coordinator-peer selectors (including reset to "No coordinator") aren't tested.

As per coding guidelines, web/**/*.{test,spec}.{js,ts,jsx,tsx}: "Write unit tests with high code coverage for critical paths".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/src/systems/network/components/shell/__tests__/channel-policy-dialog.test.tsx`
around lines 39 - 68, The ChannelPolicyDialog test only covers editing the
purpose field and misses the new fanout-policy and coordinator-peer selector
behavior. Add coverage in ChannelPolicyDialog for changing fanout_policy and
coordinator_peer_id, asserting onSubmit receives the updated values, and include
a case that resets the coordinator selection back to “No coordinator” to verify
the cleared state is submitted correctly. Use the existing render flow and
userEvent interactions in this test alongside the current onSubmit and
onOpenChange assertions.

Source: Coding guidelines

internal/network/delivery_test.go (1)

271-323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New test doesn't use t.Run("Should ...") subtests.

TestFormatNetworkMessageCapsStructuredBody is a brand-new test but is written flat (no subtests), unlike the other new test in this file (TestDeliveryCoordinatorCoalescesDigestDeliveries) which properly wraps scenarios in t.Run("Should ..."). As per path instructions, Go tests must use t.Run("Should...") subtests.

♻️ Suggested wrap
 func TestFormatNetworkMessageCapsStructuredBody(t *testing.T) {
 	t.Parallel()
-
-	envelope := Envelope{
-		...
-	}
-	...
+
+	t.Run("Should cap oversized structured trace body and mark truncation", func(t *testing.T) {
+		t.Parallel()
+
+		envelope := Envelope{
+			...
+		}
+		...
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/network/delivery_test.go` around lines 271 - 323,
`TestFormatNetworkMessageCapsStructuredBody` should be wrapped in a
`t.Run("Should ...")` subtest to match the test style used by
`TestDeliveryCoordinatorCoalescesDigestDeliveries` and the repo’s Go test
conventions. Keep the existing assertions and setup inside a single `t.Run`
block, using a descriptive “Should…” name so the scenario is explicit and
consistent with nearby tests.

Source: Coding guidelines

internal/api/core/network_details.go (1)

792-794: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated participant-recording loop across three functions.

applyNetworkChannelMessages, summarizeNetworkMessageHistory, and summarizeHistoricalParticipantCount each independently repeat the same "record PeerFrom, PeerTo, then each Mentions entry" pattern. Consider extracting a shared helper (e.g., recordMessageParticipants(recorder func(string), message store.NetworkMessageEntry)) to keep the three call sites in sync as the set of participant sources evolves.

Also applies to: 1429-1431, 1466-1468

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/core/network_details.go` around lines 792 - 794, The
participant-recording logic is duplicated in applyNetworkChannelMessages,
summarizeNetworkMessageHistory, and summarizeHistoricalParticipantCount, so
extract a shared helper (for example, a recordMessageParticipants-style
function) that records PeerFrom, PeerTo, and each Mentions entry in one place.
Update those three call sites to use the helper and keep the behavior identical
so any future participant-source changes only need to be made once.
web/src/systems/network/components/composer/composer.tsx (1)

77-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a semantic role for the mentions container.

The <div aria-label="Mention recipients"> has no ARIA role, so some screen readers may not announce the label. Adding role="group" (or role="list" with role="listitem" on each Pill) would make it reliably announced.

♿ Optional fix
       {state.mentions.length > 0 ? (
         <div
           aria-label="Mention recipients"
+          role="group"
           className="flex min-h-5 flex-wrap items-center gap-1.5"
           data-testid={`network-composer-mentions-${testIdSuffix}`}
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/systems/network/components/composer/composer.tsx` around lines 77 -
97, The mentions container in composer.tsx uses aria-label without a semantic
role, so screen readers may not announce it reliably. Update the container in
the mentions render block to use a proper role such as group, or switch to
list/listitem semantics if that better fits the Pills. Keep the existing
aria-label and ensure the role is applied where state.mentions is rendered so
the mention recipients section is announced consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dada6689-d3e8-4cae-b598-b229eb1ff259

📥 Commits

Reviewing files that changed from the base of the PR and between 00a42bb and 1a043a3.

⛔ Files ignored due to path filters (28)
  • internal/tools/builtin/testdata/native-tool-catalog.json is excluded by !**/*.json
  • openapi/agh.json is excluded by !**/*.json
  • packages/site/content/protocol/delivery.mdx is excluded by !**/*.mdx
  • packages/site/content/protocol/envelope.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/channels/meta.json is excluded by !**/*.json
  • packages/site/content/runtime/cli-reference/network/channels/update.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/digest-mode.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/meta.json is excluded by !**/*.json
  • packages/site/content/runtime/cli-reference/network/mute.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/send.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/subscribe.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/subscriptions.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/threads/meta.json is excluded by !**/*.json
  • packages/site/content/runtime/cli-reference/network/threads/promote.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/network/unmute.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/task/fan-out.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/cli-reference/task/meta.json is excluded by !**/*.json
  • packages/site/content/runtime/cli-reference/task/promote.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/network/channels-and-peers.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/network/delivery-and-safety.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/network/task-ingress.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/network/threads.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/guides/coordinate-agents-over-network.mdx is excluded by !**/*.mdx
  • sdk/typescript/src/generated/contracts.ts is excluded by !**/generated/**
  • skills/agh/references/native-tools.md is excluded by !**/*.md
  • skills/agh/references/network.md is excluded by !**/*.md
  • skills/agh/references/tasks-and-orchestration.md is excluded by !**/*.md
  • web/src/generated/agh-openapi.d.ts is excluded by !**/generated/**
📒 Files selected for processing (145)
  • internal/acp/types.go
  • internal/api/contract/contract.go
  • internal/api/contract/responses.go
  • internal/api/contract/settings.go
  • internal/api/contract/tasks.go
  • internal/api/core/agent_channels.go
  • internal/api/core/agent_channels_internal_test.go
  • internal/api/core/conversions.go
  • internal/api/core/interfaces.go
  • internal/api/core/memory.go
  • internal/api/core/network.go
  • internal/api/core/network_conversations.go
  • internal/api/core/network_details.go
  • internal/api/core/network_test.go
  • internal/api/core/prompt_stream.go
  • internal/api/core/settings.go
  • internal/api/core/settings_internal_test.go
  • internal/api/core/settings_test.go
  • internal/api/core/tasks.go
  • internal/api/core/tasks_test.go
  • internal/api/core/test_helpers_test.go
  • internal/api/core/tools.go
  • internal/api/httpapi/handlers_test.go
  • internal/api/httpapi/routes.go
  • internal/api/spec/spec.go
  • internal/api/testutil/network_stub.go
  • internal/api/udsapi/handlers_test.go
  • internal/api/udsapi/routes.go
  • internal/cli/agent_kernel.go
  • internal/cli/client.go
  • internal/cli/helpers_test.go
  • internal/cli/network.go
  • internal/cli/session.go
  • internal/cli/task.go
  • internal/config/config.go
  • internal/config/merge.go
  • internal/config/task_orchestration.go
  • internal/config/tool_surface.go
  • internal/daemon/boot.go
  • internal/daemon/composed_assembler_test.go
  • internal/daemon/daemon.go
  • internal/daemon/daemon_test.go
  • internal/daemon/harness_context.go
  • internal/daemon/native_create_tools.go
  • internal/daemon/native_tools.go
  • internal/daemon/network_response_register_prompt.go
  • internal/daemon/network_task_status_observer.go
  • internal/daemon/network_task_status_observer_test.go
  • internal/daemon/prompt_input_composite.go
  • internal/daemon/prompt_sections.go
  • internal/daemon/task_event_bridge_notifier.go
  • internal/daemon/task_role_runtime.go
  • internal/daemon/task_role_runtime_test.go
  • internal/daemon/task_runtime.go
  • internal/daemon/truncate_utf8.go
  • internal/network/audit.go
  • internal/network/delivery.go
  • internal/network/delivery_test.go
  • internal/network/envelope.go
  • internal/network/hooks_test.go
  • internal/network/manager.go
  • internal/network/manager_test.go
  • internal/network/router.go
  • internal/network/router_test.go
  • internal/network/transport_test.go
  • internal/network/validate.go
  • internal/observe/tasks.go
  • internal/situation/task_context.go
  • internal/store/globaldb/global_db.go
  • internal/store/globaldb/global_db_network_channels.go
  • internal/store/globaldb/global_db_network_channels_test.go
  • internal/store/globaldb/global_db_network_conversation_repository_test.go
  • internal/store/globaldb/global_db_network_conversations.go
  • internal/store/globaldb/global_db_network_messages.go
  • internal/store/globaldb/global_db_network_messages_test.go
  • internal/store/globaldb/global_db_network_preferences.go
  • internal/store/globaldb/global_db_network_token_stats.go
  • internal/store/globaldb/global_db_task.go
  • internal/store/globaldb/global_db_task_aux.go
  • internal/store/globaldb/global_db_task_claim.go
  • internal/store/globaldb/global_db_task_force.go
  • internal/store/globaldb/global_db_task_review.go
  • internal/store/globaldb/global_db_task_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/store/globaldb/migrate_task_run_status.go
  • internal/store/network_conversation_types_test.go
  • internal/store/store.go
  • internal/store/types.go
  • internal/task/designation.go
  • internal/task/interfaces.go
  • internal/task/interfaces_integration_test.go
  • internal/task/live.go
  • internal/task/manager.go
  • internal/task/manager_test.go
  • internal/task/types.go
  • internal/tools/builtin/builtin_test.go
  • internal/tools/builtin/descriptors.go
  • internal/tools/builtin/extensions.go
  • internal/tools/builtin/network.go
  • internal/tools/builtin/tasks.go
  • internal/tools/builtin/toolsets.go
  • internal/tools/builtin_ids.go
  • web/src/hooks/routes/__tests__/use-settings-network-page.test.tsx
  • web/src/hooks/routes/use-task-detail-orchestration-tab.ts
  • web/src/routes/_app/settings/__tests__/-network.test.tsx
  • web/src/routes/_app/settings/network.tsx
  • web/src/routes/_app/tasks.$id.tsx
  • web/src/systems/network/adapters/network-api.ts
  • web/src/systems/network/components/composer/__tests__/channel-thread-composer.test.tsx
  • web/src/systems/network/components/composer/__tests__/composer.test.tsx
  • web/src/systems/network/components/composer/channel-thread-composer.tsx
  • web/src/systems/network/components/composer/composer.tsx
  • web/src/systems/network/components/composer/detail-composer.tsx
  • web/src/systems/network/components/composer/use-composer-state.ts
  • web/src/systems/network/components/shell/__tests__/channel-policy-dialog.test.tsx
  • web/src/systems/network/components/shell/channel-header.tsx
  • web/src/systems/network/components/shell/channel-policy-dialog.tsx
  • web/src/systems/network/components/thread-overlay/__tests__/thread-overlay-header.test.tsx
  • web/src/systems/network/components/thread-overlay/__tests__/thread-subscription-control.test.tsx
  • web/src/systems/network/components/thread-overlay/thread-overlay-header.tsx
  • web/src/systems/network/components/thread-overlay/thread-overlay.tsx
  • web/src/systems/network/components/thread-overlay/thread-subscription-control.tsx
  • web/src/systems/network/components/thread-overlay/use-thread-overlay-view.ts
  • web/src/systems/network/hooks/use-network-actions.ts
  • web/src/systems/network/index.ts
  • web/src/systems/network/lib/query-keys.ts
  • web/src/systems/network/lib/query-options.ts
  • web/src/systems/network/mocks/__tests__/network-mocks.test.ts
  • web/src/systems/network/mocks/fixtures.ts
  • web/src/systems/network/mocks/handlers.ts
  • web/src/systems/network/types.ts
  • web/src/systems/settings/mocks/fixtures.ts
  • web/src/systems/tasks/adapters/tasks-api.ts
  • web/src/systems/tasks/components/__tests__/tasks-detail-orchestration-panel.test.tsx
  • web/src/systems/tasks/components/__tests__/tasks-detail-runs-panel.test.tsx
  • web/src/systems/tasks/components/__tests__/tasks-fan-out-runs-card.test.tsx
  • web/src/systems/tasks/components/stories/task-overview-components.stories.tsx
  • web/src/systems/tasks/components/tasks-detail-orchestration-panel.tsx
  • web/src/systems/tasks/components/tasks-detail-runs-panel.tsx
  • web/src/systems/tasks/components/tasks-fan-out-runs-card.tsx
  • web/src/systems/tasks/hooks/use-task-actions.ts
  • web/src/systems/tasks/hooks/use-tasks-fan-out-runs-card.ts
  • web/src/systems/tasks/index.ts
  • web/src/systems/tasks/mocks/handlers.ts
  • web/src/systems/tasks/types.ts

Comment thread internal/api/core/network_conversations.go
Comment thread internal/api/core/network_details.go
Comment thread internal/api/core/network_test.go
Comment thread internal/api/core/tasks.go
Comment thread internal/api/core/tasks.go
Comment thread internal/daemon/network_task_status_observer.go Outdated
Comment thread internal/daemon/network_task_status_observer.go Outdated
Comment thread internal/network/manager.go Outdated
Comment thread internal/network/router.go
Comment thread internal/store/globaldb/global_db_task_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
internal/task/designation_test.go (1)

8-50: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing test case for partial designation (group set, metadata unparsable).

Per DesignationFromRun's documented behavior, ok=true is returned whenever DesignationGroupID is non-empty — even when Metadata fails to parse as JSON, in which case Index/Brief fall back to zero values. This means ApplyRunDesignationSummary would silently overwrite a summary's existing Designation with an empty one in that scenario. Add a test case exercising a run with a non-empty DesignationGroupID but missing/invalid Metadata to lock in the intended fallback behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/task/designation_test.go` around lines 8 - 50, Add a test in
TestApplyRunDesignationSummary that covers a run with a non-empty
DesignationGroupID but missing or invalid Metadata, since DesignationFromRun
returns ok=true in that case and ApplyRunDesignationSummary should preserve the
group while falling back to zero-value designation fields. Use the existing
ApplyRunDesignationSummary and RunSummary symbols to verify the summary gets the
run’s DesignationGroupID and an empty/default RunDesignationSummary rather than
remaining stale or failing.
internal/store/globaldb/global_db_test.go (1)

460-460: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: extract magic number 25 into a named constant.

Unlike the sibling prefix test (line 409) which derives its cutoff from expectedGlobalMigrationPrefix(), this test hardcodes 25. A named constant (e.g., preNetworkActivationMigrationCount) would make the intent clearer and reduce the risk of silent drift if someone edits nearby test code without understanding what 25 represents.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/globaldb/global_db_test.go` at line 460, The test in
global_db_test.go uses a hardcoded migration slice cutoff of 25, which should be
replaced with a named constant to make the intent explicit and avoid silent
drift. Update the RunMigrations call site in the global DB test to use a
descriptive constant such as preNetworkActivationMigrationCount, and define it
near the sibling migration-prefix test logic so the cutoff is easy to understand
and maintain.
internal/network/router.go (1)

1237-1240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the swallowed routing-resolution errors.

Both fallback branches (thread-participant lookup failure and fanout-policy lookup failure) discard err entirely before defaulting to a degraded routing mode. This is the right fail-open behavior (fixes the earlier message-drop issue), but a persistent DB failure here will silently and permanently downgrade delivery mode (e.g., coordinator/all_members silently becomes capability_match) with no diagnostic trail. Consider a slog.Warn call before defaulting so operators can detect the underlying resolver failure.

Also applies to: 1285-1289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/network/router.go` around lines 1237 - 1240, The fallback in router
resolution is swallowing resolver errors, so add a warning log before defaulting
to degraded delivery. In router.go, update the thread-participant path in the
routing logic around threadParticipantSet, and the fanout-policy fallback path
in the same router flow, to emit a slog.Warn with the caught err and enough
context (such as envelope/target identifiers) before calling the empty/default
delivery selector. Keep the fail-open behavior, but ensure both fallback
branches preserve a diagnostic trail when the lookup fails.
internal/daemon/network_task_status_observer.go (1)

42-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider functional options instead of growing positional parameters.

newNetworkTaskStatusObserver now takes 6 positional args after this change (adding queueSize, timeout). Router in this same PR (internal/network/router.go) already uses the functional-options pattern for equivalent constructor knobs. Adopting the same pattern here (newNetworkTaskStatusObserver(networkRuntime, tasks, opts ...Option)) would improve extensibility and match repo convention.

Based on learnings and coding guidelines, "Use functional options for constructors — NewManager(opts ...Option)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/daemon/network_task_status_observer.go` around lines 42 - 80,
newNetworkTaskStatusObserver is becoming harder to extend because it now relies
on many positional parameters; switch it to a functional-options constructor to
match the repo’s constructor pattern used by Router and NewManager. Update the
observer constructor to accept opts ...Option, move queueSize and timeout into
options with sensible defaults, and keep the existing nil/default handling
inside newNetworkTaskStatusObserver so call sites stay readable and future knobs
can be added without changing the signature.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/daemon/native_create_tools_test.go`:
- Around line 198-260: The callback closures in TestNativeNetworkChannelUpdate
are using the parent t.Fatalf/t.Fatal, which can break parallel subtest
execution and cause Goexit-style panics. Update the StubNetworkStore callbacks
(GetNetworkChannelFn, PatchNetworkChannelFn, WriteNetworkChannelFn) to avoid
calling Fatal/Fatalf on the outer test; instead, capture the unexpected
condition and assert it from the subtest’s own t, or return an error that the
subtest can check.

In `@internal/store/globaldb/global_db_network_channels.go`:
- Around line 103-161: PatchNetworkChannel currently reads the channel,
validates the merged snapshot, and then writes it with a separate ExecContext
call, which allows concurrent updates to violate the coordinator/fanout
invariant. Update GlobalDB.PatchNetworkChannel to perform the GetNetworkChannel,
patch.Apply, Validate, and UPDATE within one transaction (or add a DB-level
constraint/trigger) so coordinator_peer_id cannot be left empty when
fanout_policy is Coordinator. Use the existing PatchNetworkChannel,
GetNetworkChannel, and patch.Apply flow as the main place to fix the race.

---

Nitpick comments:
In `@internal/daemon/network_task_status_observer.go`:
- Around line 42-80: newNetworkTaskStatusObserver is becoming harder to extend
because it now relies on many positional parameters; switch it to a
functional-options constructor to match the repo’s constructor pattern used by
Router and NewManager. Update the observer constructor to accept opts ...Option,
move queueSize and timeout into options with sensible defaults, and keep the
existing nil/default handling inside newNetworkTaskStatusObserver so call sites
stay readable and future knobs can be added without changing the signature.

In `@internal/network/router.go`:
- Around line 1237-1240: The fallback in router resolution is swallowing
resolver errors, so add a warning log before defaulting to degraded delivery. In
router.go, update the thread-participant path in the routing logic around
threadParticipantSet, and the fanout-policy fallback path in the same router
flow, to emit a slog.Warn with the caught err and enough context (such as
envelope/target identifiers) before calling the empty/default delivery selector.
Keep the fail-open behavior, but ensure both fallback branches preserve a
diagnostic trail when the lookup fails.

In `@internal/store/globaldb/global_db_test.go`:
- Line 460: The test in global_db_test.go uses a hardcoded migration slice
cutoff of 25, which should be replaced with a named constant to make the intent
explicit and avoid silent drift. Update the RunMigrations call site in the
global DB test to use a descriptive constant such as
preNetworkActivationMigrationCount, and define it near the sibling
migration-prefix test logic so the cutoff is easy to understand and maintain.

In `@internal/task/designation_test.go`:
- Around line 8-50: Add a test in TestApplyRunDesignationSummary that covers a
run with a non-empty DesignationGroupID but missing or invalid Metadata, since
DesignationFromRun returns ok=true in that case and ApplyRunDesignationSummary
should preserve the group while falling back to zero-value designation fields.
Use the existing ApplyRunDesignationSummary and RunSummary symbols to verify the
summary gets the run’s DesignationGroupID and an empty/default
RunDesignationSummary rather than remaining stale or failing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d16614eb-7661-4d6e-be26-6c148b653368

📥 Commits

Reviewing files that changed from the base of the PR and between 1a043a3 and 06be2f7.

⛔ Files ignored due to path filters (7)
  • internal/tools/builtin/testdata/native-tool-catalog.json is excluded by !**/*.json
  • openapi/agh.json is excluded by !**/*.json
  • packages/site/content/runtime/core/configuration/config-toml.mdx is excluded by !**/*.mdx
  • skills/agh/references/native-tools.md is excluded by !**/*.md
  • skills/agh/references/network.md is excluded by !**/*.md
  • skills/agh/references/tasks-and-orchestration.md is excluded by !**/*.md
  • web/src/generated/agh-openapi.d.ts is excluded by !**/generated/**
📒 Files selected for processing (58)
  • internal/api/core/agent_channels_internal_test.go
  • internal/api/core/network_conversations.go
  • internal/api/core/network_details.go
  • internal/api/core/network_internal_test.go
  • internal/api/core/network_test.go
  • internal/api/core/tasks.go
  • internal/api/core/tasks_test.go
  • internal/api/spec/spec.go
  • internal/api/spec/spec_test.go
  • internal/api/testutil/network_stub.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/merge.go
  • internal/config/task_orchestration.go
  • internal/config/task_orchestration_test.go
  • internal/config/tool_surface.go
  • internal/daemon/composed_assembler_test.go
  • internal/daemon/daemon_test.go
  • internal/daemon/native_create_tools.go
  • internal/daemon/native_create_tools_test.go
  • internal/daemon/native_tools.go
  • internal/daemon/native_tools_test.go
  • internal/daemon/network_task_status_observer.go
  • internal/daemon/network_task_status_observer_test.go
  • internal/daemon/task_event_bridge_notifier.go
  • internal/daemon/task_role_runtime.go
  • internal/daemon/task_role_runtime_test.go
  • internal/network/delivery.go
  • internal/network/delivery_test.go
  • internal/network/manager.go
  • internal/network/manager_test.go
  • internal/network/router.go
  • internal/network/router_test.go
  • internal/observe/tasks.go
  • internal/store/globaldb/global_db.go
  • internal/store/globaldb/global_db_network_channels.go
  • internal/store/globaldb/global_db_network_channels_test.go
  • internal/store/globaldb/global_db_network_messages_test.go
  • internal/store/globaldb/global_db_task_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/store/network_conversation_types_test.go
  • internal/store/store.go
  • internal/store/types.go
  • internal/task/designation.go
  • internal/task/designation_test.go
  • internal/task/live.go
  • internal/task/manager.go
  • internal/tools/builtin/tasks.go
  • internal/tools/native_test.go
  • internal/tools/schema.go
  • web/src/hooks/routes/__tests__/use-task-detail-orchestration-tab.test.tsx
  • web/src/hooks/routes/use-task-detail-orchestration-tab.ts
  • web/src/systems/network/components/composer/__tests__/composer.test.tsx
  • web/src/systems/network/components/composer/composer.tsx
  • web/src/systems/network/components/shell/__tests__/channel-policy-dialog.test.tsx
  • web/src/systems/network/components/shell/channel-policy-dialog.tsx
  • web/src/systems/network/components/thread-overlay/__tests__/thread-subscription-control.test.tsx
  • web/src/systems/network/components/thread-overlay/thread-subscription-control.tsx
🚧 Files skipped from review as they are similar to previous changes (29)
  • internal/store/globaldb/global_db_network_messages_test.go
  • internal/daemon/task_role_runtime_test.go
  • internal/config/merge.go
  • internal/config/tool_surface.go
  • internal/store/globaldb/global_db_task_test.go
  • internal/daemon/daemon_test.go
  • internal/daemon/native_create_tools.go
  • internal/store/network_conversation_types_test.go
  • internal/task/manager.go
  • internal/daemon/task_event_bridge_notifier.go
  • internal/config/task_orchestration.go
  • internal/daemon/task_role_runtime.go
  • internal/daemon/composed_assembler_test.go
  • internal/network/router_test.go
  • internal/api/spec/spec.go
  • internal/api/core/tasks.go
  • internal/tools/builtin/tasks.go
  • internal/api/core/agent_channels_internal_test.go
  • internal/config/config.go
  • internal/api/core/tasks_test.go
  • internal/network/manager.go
  • internal/api/core/network_test.go
  • internal/store/types.go
  • internal/api/core/network_details.go
  • internal/network/delivery_test.go
  • internal/daemon/native_tools.go
  • internal/api/testutil/network_stub.go
  • internal/api/core/network_conversations.go
  • internal/network/delivery.go

Comment thread internal/daemon/native_create_tools_test.go
Comment thread internal/store/globaldb/global_db_network_channels.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/store/globaldb/global_db_network_channels.go (1)

238-244: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant branching in error handling — both paths are identical.

Both the errors.Is(err, sql.ErrNoRows) branch and the fallback return the exact same value. Simplify.

♻️ Simplify redundant branch
 	entry, err := scanNetworkChannel(row)
 	if err != nil {
-		if errors.Is(err, sql.ErrNoRows) {
-			return store.NetworkChannelEntry{}, err
-		}
-		return store.NetworkChannelEntry{}, err
+		return store.NetworkChannelEntry{}, err
 	}
 	return entry, nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/globaldb/global_db_network_channels.go` around lines 238 -
244, The error handling in the scanNetworkChannel path has a redundant
sql.ErrNoRows branch because both branches return the same
store.NetworkChannelEntry{} and err. Simplify the logic in the function that
processes scanNetworkChannel results by removing the errors.Is(err,
sql.ErrNoRows) special case and just returning the empty entry plus the original
error from the single err check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/store/globaldb/global_db_network_channels.go`:
- Around line 238-244: The error handling in the scanNetworkChannel path has a
redundant sql.ErrNoRows branch because both branches return the same
store.NetworkChannelEntry{} and err. Simplify the logic in the function that
processes scanNetworkChannel results by removing the errors.Is(err,
sql.ErrNoRows) special case and just returning the empty entry plus the original
error from the single err check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4f8c2721-6e68-4faf-aada-8dbceaaef677

📥 Commits

Reviewing files that changed from the base of the PR and between 06be2f7 and ad93503.

📒 Files selected for processing (11)
  • internal/daemon/native_create_tools_test.go
  • internal/daemon/network_task_status_observer.go
  • internal/daemon/network_task_status_observer_test.go
  • internal/daemon/task_event_bridge_notifier.go
  • internal/network/manager.go
  • internal/network/router.go
  • internal/network/router_test.go
  • internal/store/globaldb/global_db_network_channels.go
  • internal/store/globaldb/global_db_network_channels_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/task/designation_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • internal/daemon/native_create_tools_test.go
  • internal/store/globaldb/global_db_network_channels_test.go
  • internal/network/router_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/daemon/task_event_bridge_notifier.go
  • internal/daemon/network_task_status_observer_test.go
  • internal/network/manager.go
  • internal/network/router.go

@pedronauck
pedronauck merged commit 65410d6 into main Jul 2, 2026
11 checks passed
@pedronauck
pedronauck deleted the network-opts branch July 2, 2026 13:44
pedronauck added a commit that referenced this pull request Jul 4, 2026
Curated release highlights for v0.0.9 covering task blocking/recovery and
needs-attention triage (#269), network delivery policies, subscriptions and
thread-to-task promotion (#263), and clearer native tool errors and
availability diagnostics (#266).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
pedronauck added a commit that referenced this pull request Jul 4, 2026
The release-only Full Integration Suite failed on main because
integration-tagged tests never ran in the feature PRs' regular CI and drifted
from #263's intentional network changes (each confirmed by passing unit tests
in composed_assembler_test and router_test):

- globaldb: pass &Summary to assertTaskSummaryMatchesTask; ListTasks returns
  []Summary by value. Fixes an integration-only build failure.
- harness prompt: startup/resume prompts embed the compact network response
  register, not the full bundled network.md; network-turn augmenters include
  network_response_register.
- prompt composite / harness: register the network_response_register
  descriptor (mirrors boot.go) so the resolver can enable it on network turns
  without tripping the "enabled but not registered" guard.
- daemon + cli delivery: default channel fanout is capability_match, which
  digests non-activating broadcasts, so mention the recipient to exercise the
  full inbound-delivery path.

Production code unchanged. Verified under -race: globaldb 512, daemon
network/harness/prompt 147, cli network 32.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
pedronauck added a commit that referenced this pull request Jul 4, 2026
## Release v0.0.9

This PR prepares the release of version v0.0.9.

### Changelog

## 0.0.9 - 2026-07-04



### ♻️ Refactoring

- Tasks orchestration (#269)



### 🐛 Bug Fixes

- Network optimizations (#263)
- Native tools (#266)
- Align release-gated network integration tests with #263 behavior



### 📚 Documentation

- Update skills
- New skill
- Add feature stories
- Add v0.0.9 release notes



### 📦 Build System

- Update go deps
- Gitignore

### Release Notes

#### Features

##### Network delivery policies, subscriptions, and thread-to-task
promotion
AGH network channels gain durable delivery control. Each channel now
carries a fan-out policy — deliver to all members, route to a designated
coordinator peer, or match by capability (the default) — managed with
`agh network channels create` and `agh network channels update`. Peers
and tasks can subscribe to channels: inspect subscriptions with `agh
network subscriptions` and manage per-task subscriptions with `agh task
subscribe <task-id>`. Executable thread messages can be promoted into
durable workspace tasks straight from the CLI with `agh network
promote`, or by an agent through the `agh__task_promote_from_thread`
native tool, and designated sibling task runs can be fanned out as part
of a workflow. Network and task views now surface task links, peer and
coordination cost, and delivered size and token metrics, while mentions
are normalized (trimmed, empties removed) and size and token limits are
enforced as non-negative.

##### Task blocking, recovery, and needs-attention triage
Tasks are now first-class to block, triage, and recover. A task can be
explicitly blocked and unblocked with `agh task block <id>` and `agh
task unblock <id>`, and every block is kept as durable history you can
inspect with `agh task blocks <id>`. A new `needs_attention` status
surfaces tasks that stalled and need a human or coordinator to step in —
task details now carry the blocked reason and the wake creator so it is
clear why a task is waiting and who nudged it. Clear the state with `agh
task recover <id>`, recover a specific stalled run with `agh task run
recover <run-id>`, or let an agent do it through the `agh__task_recover`
native tool and the HTTP/UDS API. Task completion now returns the IDs of
any tasks it created, and stronger validation rejects invalid
created-task references before they are persisted. Claim tokens are
redacted across task outputs and hook payloads, and the new
`needs_attention_after` setting controls how long a task may wait before
it is flagged for attention.

#### Fixes

##### Clearer native tool errors and availability diagnostics
Native and hosted tool calls now fail legibly. Hosted MCP tool calls
return richer, structured JSON error details — the tool ID, an error
code, and any denial reasons — instead of opaque failures, and
advertised tools carry improved metadata and descriptions. When runtime
diagnostic data cannot be retrieved, native tool lookups report a clear
"unavailable" diagnostic rather than a silent miss. Hosted and session
MCP availability handling is safer overall, with clearer informational
and warning logs and graceful fallbacks when a feature is disabled.
Documentation and the runtime envelope now consistently direct agents to
the canonical `agh__*` tool IDs and the harness-returned tool
references.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
  * Updated a bundled web assets dependency to a newer version.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 899f0753 Deployed Jul 2, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant