+ {strainCards.slice(0, 2).map((card) => (
+
+
+ {card.spread_strain ? (
+
+ ) : null}
+
+ strain · {card.persona}
+
+
+
+
+ {card.wins.length}W · {card.losses.length}L · {card.subnets.length} subnets
+ {card.erasure_recovery_rate > 0
+ ? ` · erasure ${Math.round(card.erasure_recovery_rate * 100)}%`
+ : ''}
+
+
+ ))}
+
+ )}
{model.phenotypeSource && (
+ genealogy graft pending · tier {agent.graft_tier}
+ {agent.graft_source_strain ? ` · strain ${agent.graft_source_strain}` : ''}
+ {' '}(applies on next spread)
+
+ )}
diff --git a/server/web/src/components/Layout/Layout.tsx b/server/web/src/components/Layout/Layout.tsx
index 14a2971..cf9e3c6 100644
--- a/server/web/src/components/Layout/Layout.tsx
+++ b/server/web/src/components/Layout/Layout.tsx
@@ -12,6 +12,7 @@ import { SacredMotif } from '../Visual/sacredGeometry/motifs';
import SetupBanner from '../SetupBanner';
import { getSetupStatus } from '../../help/setupStatus';
import { resolvePageWeather } from '../../help/pageWeather';
+import { mergeScoutBiomeWeather, type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather';
import { isDashboardRoute } from '../../help/routeEffects';
import { api } from '../../api/client';
import { usePresence } from '../../context/PresenceContext';
@@ -40,10 +41,11 @@ function operatorDeckId(pathname: string): string {
if (path.startsWith('/lotl-timeline') || path.startsWith('/onion')) return 'lotl-timeline';
if (path.startsWith('/roi')) return 'roi';
if (path.startsWith('/activity')) return 'activity';
+ if (path.startsWith('/seer')) return 'seer';
return 'dashboard';
}
-const NAV = [
+const NAV_BASE = [
{ to: '/dashboard', label: 'Command Deck', icon: 'deck' },
{ to: '/crucible', label: 'Crucible', icon: 'crucible' },
{ to: '/activity', label: 'Activity Feed', icon: 'activity' },
@@ -57,6 +59,20 @@ const NAV = [
{ to: '/settings', label: 'Calibrate', icon: 'gear' },
] as const;
+const SEER_NAV = { to: '/seer', label: 'Seer', icon: 'seer' } as const;
+
+function buildNav(aiControlEnabled: boolean) {
+ if (!aiControlEnabled) {
+ return [...NAV_BASE];
+ }
+ const items = [...NAV_BASE];
+ const calibrateIdx = items.findIndex((i) => i.to === '/settings');
+ items.splice(calibrateIdx, 0, SEER_NAV);
+ return items;
+}
+
+const NAV = NAV_BASE;
+
const DOCS_HREF = '/docs/';
/** Primary tabs on mobile bottom bar — Deck, Crucible, Activity, ROI, Onion */
@@ -150,6 +166,14 @@ function NavIcon({ type }: { type: string }) {
);
+ case 'seer':
+ return (
+
+ );
case 'docs':
return (
diff --git a/server/web/src/types/index.ts b/server/web/src/types/index.ts
index eca2987..4d8104f 100644
--- a/server/web/src/types/index.ts
+++ b/server/web/src/types/index.ts
@@ -118,11 +118,43 @@ export interface Agent {
parent_agent_id?: string;
spread_generation?: number;
spread_strain?: string;
+ graft_source_strain?: string;
+ graft_tier?: string;
+ graft_approved_at?: string;
/** Cloned fleet phenotype from a sibling with the same host fingerprint. */
inherited_phenotype?: InheritedPhenotype;
}
+/** Light gamification card for a winning spread tree lineage. */
+export interface StrainCard {
+ id: string;
+ root_agent_id: string;
+ source_agent_id: string;
+ source_agent_name: string;
+ spread_strain: string;
+ spread_lane: string;
+ persona: string;
+ parents: StrainCardParent[];
+ wins: string[];
+ losses: string[];
+ subnets: string[];
+ erasure_recovery_rate: number;
+ peak_hashrate: number;
+ tier_order: string[];
+ tree_size: number;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface StrainCardParent {
+ agent_id: string;
+ agent_name?: string;
+ join_lane?: string;
+ spread_lane?: string;
+ generation?: number;
+}
+
export interface InheritedPhenotype {
source_agent_name: string;
fingerprint?: string;
@@ -482,6 +514,24 @@ export interface AIDecisionRecord {
judge_verdict?: string;
}
+/** Seer LLM stream event — GET /api/v1/seer/stream */
+export interface SeerEventRecord {
+ id: number;
+ event_type: string;
+ agent_id?: string;
+ payload: unknown;
+ ts?: string;
+}
+
+/** Seer persisted AI memory note */
+export interface SeerNoteRecord {
+ id: number;
+ agent_id?: string;
+ note: string;
+ source?: string;
+ ts?: string;
+}
+
export interface EarningsEstimate {
hashrate: number;
xmr_per_day: number;
@@ -697,6 +747,38 @@ export interface SpreadRouteRecommendation {
erasure_lanes_enabled?: boolean;
}
+export interface SubnetAutopsyAttempt {
+ agent_id?: string;
+ agent_name?: string;
+ tier: string;
+ ok: boolean;
+ error?: string;
+ duration_ms?: number;
+ phase?: string;
+}
+
+export interface SubnetAutopsyPacket {
+ prefix: string;
+ triggered_at: string;
+ fail_count: number;
+ paused_until?: string;
+ lotl_attempts: SubnetAutopsyAttempt[];
+ wsus_mimic: {
+ format_mimic_enabled: boolean;
+ cache_peer_lane: string;
+ recent_join_lane?: string;
+ };
+ persona: string;
+ erasure_fallback: {
+ erasure_lanes_enabled: boolean;
+ available_as_fallback: boolean;
+ };
+ gossip_whispers: { tier: string; condition: string; reason?: string }[];
+ failure_atlas?: string;
+ cause_of_death: string;
+ vaccination_lane?: SpreadRouteRecommendation;
+}
+
export interface ServiceGraphEntry {
service_name: string;
port?: number;
diff --git a/tests/README.md b/tests/README.md
index 6280783..b912013 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -64,6 +64,7 @@ Windows dashboard only; no in-process cloudflared. Genealogy fields are **teleme
| **BGP-style spread router** | `server/internal/spreadrouter/`; Path Tracer + deploy plan `spread_route_hint` | `go test ./internal/spreadrouter/... -count=1`; `go test ./internal/api/... -run SpreadRoute -count=1` |
| **Erasure-coded multi-lane spread (foundation)** | Server `internal/erasure/` RS 4+2 encode + `erasure_plan` on deploy plans; agent `deploy/erasure_staging.go` k-of-n reassembly fallback; Calibrate `server.erasure_lanes_enabled` + Path Tracer `RS lanes` hint | `go test ./internal/erasure/... -count=1`; `go test ./internal/api/... -run Erasure -count=1`; `go test ./deploy/... -run Erasure -count=1`; `go test ./config/... ./client/... -run Erasure -count=1` |
| **Spread genealogy watermark** | Forge `-ldflags` + env overrides; auth/stats JSON only | `go test ./config/... -run Genealogy -count=1`; `go test ./internal/builder/... -run Genealogy -count=1`; `go test ./internal/api/... -run SpreadGenealogy -count=1` |
+| **Genealogy grafting** | Court `spread_graft` + L4 + hashrate gate; `POST /api/v1/fleet/graft`; auth `graft_policy` push; agent applies tier order on next spread; zero config when `ai_control_enabled` + `fleet_roles_enabled` | `go test ./internal/strategy/... -run Graft -count=1`; `go test ./internal/api/... -run FleetGraft -count=1`; `go test ./client/... -run GraftPolicy -count=1`; Vitest `AccessDepthPanel.graft.test.tsx`, `PathTracerPage` graft note |
| **Court retry + L4 elevation** | `server/internal/ai/court_commands.go`, scheduler `ensureCourtRetryClearance` | `go test ./internal/ai/... -run CourtRetry -count=1` |
| **Hashrate + subnet spread gates** | Agent `deploy/hashrate_gate.go`; server `internal/db/subnet_spread_pause.go`, `internal/atlas/subnet_immune.go` | `go test ./deploy/... -run HashrateGate -count=1`; `go test ./internal/db/... ./internal/atlas/... -run Subnet -count=1` |
| **APK scout mode** | Forge `scout_mode` / `ApkMode`; agent `client/scout_mode.go`; builder `build_apk.go` | `go test ./client/... -run Scout -count=1`; `go test ./internal/builder/... -run Apk -count=1`; `go test ./internal/api/... -run Scout -count=1` |
@@ -258,6 +259,7 @@ cd agent && go test ./client/... -run "Beacon|HandleMessage|WS" -count=1
| Scout discover skips staging | `agent/deploy/scout_discover_test.go` | 2 |
| Scout remote action gates | `agent/client/scout_mode_test.go` | 2 |
| Scout phenotype publish | `server/internal/api/scout_phenotype_test.go` | 1 |
+| Scout constellation venues (SSID → persona pack) | `server/internal/ai/scout_constellation_test.go`, `server/internal/api/scout_constellation_test.go`, `agent/client/scout_constellation_test.go`, `agent/deploy/scout_wifi_test.go`, `server/web/src/help/scoutBiomeWeather.test.ts` | 1 |
### Fleet intelligence (2026-06-07 — phenotype, atlas, court, clearance)
@@ -764,7 +766,7 @@ Optional probe env vars for Access Depth (`environment_probes`):
- `AETHERFORGE_BATTERY_OK=1`
- `AETHERFORGE_FOREGROUND_SERVICE=1`
-Forge may also bake `ApkMode` and `ScoutMode` (`-ldflags` / builder preset) so registration reports `platform=android` without runtime env. **Scout mode** (`scout_mode: true`) keeps mining off, runs `discover_and_join` + `service_graph` only, pushes phenotype via `scout_report`, and never stages spread payloads.
+Forge may also bake `ApkMode` and `ScoutMode` (`-ldflags` / builder preset) so registration reports `platform=android` without runtime env. **Scout mode** (`scout_mode: true`) keeps mining off, runs `discover_and_join` + `service_graph` only, pushes phenotype via `scout_report`, and never stages spread payloads. **Scout constellation mode** (zero config): 3+ `scout_report` hits on the same SSID within 10 minutes form a venue constellation; server infers `airport`/`campus`/`retail`/`unknown` and pushes venue persona packs via `spread_policy` + `policy_update`. APK scouts send `ssid` from `AETHERFORGE_WIFI_SSID`; Emberwake/dashboard weather-map merges active scout biomes.
Persona spread temperament (`server.ai_persona`) maps aggressive/silent/passive/persuasive/balanced to default spread tier order hints. When `ai_control_enabled` is on, auth and `policy_update` push `spread_temperament` (adaptive_strategy shape) and `FleetAISnapshot` merges it for the scheduler — AI shapes propagation personality, not just restarts.