Skip to content

harness: apply system_prompt_flag in ContainerScriptHarness.GetCommand#682

Merged
ptone merged 1 commit into
GoogleCloudPlatform:mainfrom
ptone:scion/fix-container-script-system-prompt
Jul 12, 2026
Merged

harness: apply system_prompt_flag in ContainerScriptHarness.GetCommand#682
ptone merged 1 commit into
GoogleCloudPlatform:mainfrom
ptone:scion/fix-container-script-system-prompt

Conversation

@ptone

@ptone ptone commented Jul 12, 2026

Copy link
Copy Markdown
Member

Problem

The system_prompt_flag field declared in harness config.yaml (e.g. --system-prompt for claude) was never applied to the CLI command. ContainerScriptHarness.GetCommand() built args without reading SystemPromptFlag or the staged system prompt file. The field existed in HarnessCommandConfig but was dead code.

The old builtin ClaudeCode struct handled this correctly — the container-script migration lost it.

Fix

In pkg/harness/container_script_harness.go:

  • Added agentHome field to ContainerScriptHarness, populated during Provision()
  • GetCommand() calls readSystemPrompt() after building args
  • readSystemPrompt() reads from staged inputs path first (.scion/harness/inputs/system-prompt.md), falls back to native harness location for resume scenarios
  • If SystemPromptFlag is set and file has content, appends [flag, content] to command args
  • File-not-found and empty SystemPromptFlag are silent no-ops

@google-cla

google-cla Bot commented Jul 12, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates ContainerScriptHarness to capture agentHome during provisioning and read the system prompt from staged or native files to append to the command arguments. A critical issue was identified where agentHome might not be populated during resume scenarios because Provision() is not called, which would cause the system prompt to be silently ignored. To resolve this, it is recommended to capture agentHome in other lifecycle methods like GetEnv and HasSystemPrompt.

Comment on lines +191 to +193
if c.agentHome == "" {
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If the agent is resumed, Provision() is typically not called on the newly instantiated ContainerScriptHarness. As a result, c.agentHome will remain empty, causing readSystemPrompt() to return early and silently ignore the system prompt. This defeats the fallback mechanism for resume scenarios.

To ensure c.agentHome is populated during resume scenarios, capture agentHome in other methods of ContainerScriptHarness that receive it, such as GetEnv and HasSystemPrompt.

For example, you can update those methods like this:

func (c *ContainerScriptHarness) GetEnv(agentName, agentHome, unixUsername string) map[string]string {
	c.agentHome = agentHome
	// ... existing code ...
}

func (c *ContainerScriptHarness) HasSystemPrompt(agentHome string) bool {
	c.agentHome = agentHome
	// ... existing code ...
}

@ptone ptone merged commit 6c93c20 into GoogleCloudPlatform:main Jul 12, 2026
8 of 9 checks passed
krohnjw added a commit to ParkWhiz/scion that referenced this pull request Jul 12, 2026
* feat: Model UX improvements - alias tiers, CLI flag, project defaults, and web UI dropdowns (#639)

* feat: add extra-large model alias tier with xl shorthand

Add extra-large to the canonical model alias set (small, medium, large,
extra-large). Add NormalizeModelAlias() to map the xl shorthand to
extra-large. Update ResolveModelAlias() to normalize before lookup.
Add extra-large mapping to all 6 harness configs (initially same as
large). Add unit tests for normalization and resolution.

* feat: add --model flag to scion start command

Add --model flag that accepts model aliases (small, medium, large,
extra-large/xl) or explicit model IDs. The flag value is normalized
(xl -> extra-large) and merged into the inline config, taking
precedence over --config file model values. Alias-to-concrete
resolution happens at broker provision time.

* feat: add DefaultModel to ProjectSettings for project-level model default

Add DefaultModel field to ProjectSettings struct with scion.io/default-model
annotation key. Apply in applyProjectDefaults() only when no explicit model
is set by agent/template/CLI, preserving the precedence chain.

* feat: replace free-text model input with dropdown in agent configure form

Replace the free-text model input with a dropdown matching the create form:
Small, Medium, Large, Extra Large, Other (specify). Existing concrete model
values auto-map to Other with the value pre-populated. Alias values select
the corresponding dropdown option.

* feat: add Default Model dropdown to project settings

Add Default Model dropdown to the Agent Defaults section of project settings.
Supports alias selection (Small, Medium, Large, Extra Large) and custom model
IDs via Other (specify). Persists via the defaultModel field in the project
settings API.

* feat: add model selection dropdown to agent create form

Add Model dropdown with options: Small, Medium, Large, Extra Large,
Other (specify). Other reveals a free-text input for concrete model IDs.
Selection is included in the create request config. Pre-populates from
project default model when available.

* test: add flag-config merge precedence tests for --model flag

Add tests verifying that --model flag overrides config file model value
and that xl shorthand is normalized to extra-large. Addresses reviewer
finding from mu-rev-p01.

* fix: clear custom model ID when switching away from Other in create form

Align agent-create form behavior with configure and project-settings forms
by clearing customModelId when the dropdown selection changes away from
Other. Addresses mu-rev-final finding 1.

* fix: normalize model aliases case-insensitively and handle xl shorthand

* fix: preserve original casing for custom model IDs in agent create form

---------

Co-authored-by: Scion Agent (mu-dev-p0) <agent@scion.dev>

* Two-tier settings architecture for HA multi-node deployments (#640)

* feat(store): add HubSetting schema and HubSettingStore (settings-db phase 1)

Implements Phase 1 of the two-tier settings architecture:

- New Ent schema `HubSetting` with fields: id (UUID), section (unique),
  value (JSON), revision (optimistic concurrency), updated_by, timestamps.
  Table: hub_settings. Codegen output committed.

- `HubSettingStore` interface in pkg/store/store.go with Get, List, Upsert
  (CAS semantics), and Delete operations. Domain struct `HubSetting` and
  sentinel error `ErrRevisionConflict` added to pkg/store/models.go.

- Ent-backed implementation in pkg/store/entadapter/hubsetting_store.go
  using transactions with SELECT FOR UPDATE (Postgres) / single-writer
  serialization (SQLite) for atomic CAS upserts. Wired into CompositeStore.

- Tests cover: get-missing, create, update-with-revision-bump, CAS conflict,
  create-only conflict, unconditional upsert, list, delete, updatedBy
  clearing, and concurrent CAS race (only one winner).

No behavior change — this is a new store surface only.

https://github.com/ptone/scion/issues/268

* feat(config): add opsettings section registry (settings-db phase 2)

Implement Phase 2 of the two-tier settings architecture: new package
pkg/config/opsettings providing the Layer-1 operational settings section
registry.

Deliverables:
- Section structs for all 8 Layer-1 sections (access, lifecycle,
  maintenance, telemetry, agent_defaults, endpoints, github_app,
  notifications) with json tags matching V1 yaml keys
- Section descriptor + Registry as single source of truth for
  Layer-0 vs Layer-1 classification
- Lookup helpers: SectionByName, OwningSection, IsLayer1Key,
  ClassifyKeys
- JSON schema validation per section (Validate helper returning
  ValidationError-compatible errors)
- File→section seeding converters (ExtractSectionFromKoanf,
  ExtractAllSections)
- Koanf merge helpers (LoadSectionsIntoKoanf, MergeSectionsIntoKoanf)
- Env-override detection (EnvOverriddenLayer1Keys, DetectEnvOverrides)
- 21 passing tests covering registry completeness, schema validation,
  round-trip fidelity, and env-override detection

No behavior change — self-contained new package, no modifications to
existing code.

Issue: https://github.com/ptone/scion/issues/268

* fix(store): address review findings — gofmt alignment + Postgres CAS integration test

B1 (blocking): Fixed gofmt alignment in pkg/store/store.go error sentinel
var block. Verified with `gofmt -l .` (clean on all our files).

NB1 (non-blocking): Added Postgres integration test for hub-setting CAS
contention in pkg/store/integrationtest/hubsetting_contention_test.go.
Two tests exercise the SELECT ... FOR UPDATE row-locking path:
- TestContention_HubSettingCAS: N goroutines race CAS upserts at the same
  expectedRevision; asserts exactly one winner, N-1 ErrRevisionConflict.
- TestContention_HubSettingCreateOnly: N goroutines race create-only upserts
  for the same section; asserts exactly one creator wins.
Follows existing harness conventions (build tag `integration`, requirePG via
newStore, runConcurrently barrier, concurrency(t) scaling). Compiles under
`go vet -tags integration`; CI exercises it with SCION_TEST_POSTGRES_URL.

Also fixed minor gofmt alignment in hubsetting_store_test.go.

https://github.com/ptone/scion/issues/268

* fix(config): address review findings on opsettings registry

B1: maintenance section now has empty KoanfPaths — it is runtime/API-owned
state with no settings.yaml representation. Seeding and extraction skip it
gracefully; OwningSection no longer claims fabricated keys.

B2: github_app extraction uses mapped field-by-field extraction instead of
extractSubtree, preventing private_key and webhook_secret from leaking into
section documents. New test asserts secret exclusion.

N1: Added TestRoundTripFromYAMLFile — loads a real settings.yaml through
koanf's YAML parser, extracts sections, reloads, and compares all Layer-1
leaf keys against the original. Catches KoanfPath mismatches.

N2: Added user_access_mode to serverAuth and auto_suspend_stalled to
serverHub in settings-v1.schema.json. Dropped fallback schemas.

N3: Added tech-debt comments on hand-written maintenance and github_app
schemas noting the canonical schema lacks these $defs.

N4: Removed unnecessary sync.Once from ensureIndexes (init() already
guarantees single execution).

N6: Removed dead code sectionKeyRoot and isParentKey.

N7: Added TestRoundTripDefaultResources verifying nested ResourceSpec
round-trips correctly through JSON intermediate form.

Issue: https://github.com/ptone/scion/issues/268

* chore(config): remove dead schemaPropertyOrFallback function

No longer called after N2 fix added the fields to the canonical schema.

Issue: https://github.com/ptone/scion/issues/268

* feat(hub): add OperationalSettings service (settings-db phase 3)

Implement the OperationalSettings runtime component per design §3.5:
- OperationalSettings service with Snapshot/Update/Refresh
- ApplySnapshot refactor of reloadSettings (file-mode parity preserved)
- Postgres-gated startup seeding under advisory lock
- 19 unit tests covering precedence, seeding, refresh, regression parity

Ref: https://github.com/ptone/scion/issues/268

* fix(hub): maintenance state ownership in ApplySnapshot (settings-db phase 3 fixes)

Addresses all review findings from the Phase 3 review:

B1+B2: Remove unconditional s.maintenance.Set() from ApplySnapshot.
Maintenance is runtime/API-owned state — file-mode reloadSettings must
never touch it (restoring pre-refactor behavior). Add
ApplyMaintenanceFromSnapshot for postgres-mode callers with correct
env > DB precedence (§3.4/§3.8): env SCION_SERVER_ADMIN_MODE wins over
DB value on this node (per-node break-glass). HasMaintenanceRow on
Layer1Snapshot makes absent-row semantics explicit.

NB1: Change Server.operationalSettings from *OperationalSettings to
atomic.Pointer[OperationalSettings] for race-safe concurrent access
(Phase 4/5 will add request-path readers).

NB2: Add WARN logs in LoadFileOnlyKoanf when GetGlobalDir or
loadSettingsFile fail, instead of silently swallowing errors.

NB4: Add clarifying comments on Layer1Snapshot and
BuildLayer1SnapshotFromFile explaining which fields are only populated
in postgres mode and why.

NB5: Add regression tests:
- Env force-enable wins over absent and false maintenance DB rows
- File-mode ApplySnapshot does not modify MaintenanceState (B2 scenario)
- Concurrent Refresh+Snapshot race test (safe under -race)

https://github.com/ptone/scion/issues/268

* feat(hub): settings change propagation (settings-db phase 4)

Part A cleanups:
- Release advisory seed lock immediately after seeding (scoped, not
  defer-through-Refresh) per design §3.9
- Use errors.Is(err, store.ErrNotFound) instead of direct sentinel
  comparison in seedHubSettingsIfNeeded
- Add comment documenting SCION_SERVER_ADMIN_MODE bidirectional override
  semantics in ApplyMaintenanceFromSnapshot
- Add focused unit tests for seedHubSettingsIfNeeded: _meta sentinel check,
  section extraction, maintenance skipped, github_app no secrets
- Add mixed-source snapshot test: section A from DB, section B from file

Part B propagation (design §3.6):
- Publish: on successful Update upsert (postgres mode), publish event
  admin.settings.updated with {section, revision} via the event publisher
- Subscribe: each hub replica subscribes to admin.settings.updated; on
  receipt calls Refresh() + ApplySnapshot + ApplyMaintenanceFromSnapshot
- Reconnect refresh: add SetOnReconnect callback to PostgresEventPublisher;
  on listener reconnect, run unconditional Refresh + apply to cover
  notifications missed during the gap
- Poll backstop: 60s jittered ticker calling Refresh + apply-on-change;
  postgres mode only; clean shutdown via context cancellation
- Self-apply: writing node applies its own change synchronously in Update
  (via Snapshot + ApplySnapshot); double-apply from own event is idempotent
- SQLite/file mode: event publisher is nil, no subscription/ticker started

Tests (all run with -race):
- Publish-on-update emits correct subject/payload
- No-publish when no event publisher (SQLite mode)
- Subscription triggers Refresh and applies changed sections
- Maintenance propagation applies when env override absent
- Maintenance NOT overridden when env var set (break-glass)
- Reconnect callback triggers unconditional refresh
- Poll backstop detects revision bump
- Idempotency: double-apply is harmless
- SQLite mode: no publisher, no subscription, no ticker
- Self-apply on writing node (config + maintenance)
- Concurrent propagation race test

https://github.com/ptone/scion/issues/268

* feat(hub): DB-backed admin settings API (settings-db phase 5)

Part A — Phase 4 review cleanups:
- A1: Use propCtx (not parent ctx) in SetOnReconnect callback
- A2: Run onReconnect callbacks asynchronously to avoid blocking
  the listener loop
- A3: Add panic recovery to propagation goroutines (subscription
  loop and poll backstop)

Part B — Admin API with DB backing (postgres mode only):
- GET/PUT /api/v1/admin/server-config with Layer-0/Layer-1
  partitioning: Layer-0 fields read from file (read-only via API),
  Layer-1 fields read/written from hub_settings table
- GET/PUT /api/v1/admin/maintenance persisted to hub_settings
  and propagated cluster-wide via LISTEN/NOTIFY
- Section metadata (source, revision, updated_at, updated_by)
  on every GET response
- env_overrides map showing environment variable drift warnings
- CAS via expected_revisions map in request body (per-section
  revision matching)
- File/SQLite mode retains exact existing behavior (no dispatch)
- Existing secret masking behavior unchanged

Resolves: https://github.com/ptone/scion/issues/268

* fix(hub): admin API response fidelity, error hygiene, key classification (settings-db phase 5 fixes)

Address all 9 review findings from the Phase 5 independent review:

Blocking:
- B1: applySnapshotToResponse now unconditionally sets boolean fields
  (AutoSuspendStalled, SoftDeleteRetainFiles) and all other fields from the
  snapshot, so false/empty DB values correctly override stale file values.
- B2: PUT and maintenance handlers log full errors server-side (slog.Error)
  and return generic messages without raw err content for all 500 responses.
- B3: Three-way key classification (Layer-1/Layer-0/unclassified). Explicit
  Layer-0 bootstrap set derived from design §3.1. Unclassified keys (runtimes,
  harness_configs, profiles, schema_version, active_profile, workspace_path)
  are ignored with slog.Warn + ignored_keys response field, preserving shape
  compatibility with file mode and the web UI which sends these fields.

Non-blocking:
- N1: Section iteration in sorted order for deterministic partial-apply/CAS.
- N2: HTTP request context threaded into buildSectionMetadata (no more
  context.Background()).
- N3: Panic recovery in reconnect-callback goroutine (events_postgres.go).
  WaitGroup tracking omitted — the callback is idempotent and short-lived.
- N4: sectionState cache enriched with UpdatedAt/UpdatedBy; buildSectionMetadata
  serves metadata from cache, eliminating the extra per-GET DB round-trip.
- N5: Removed unused import suppressors (bytes, store) from test file.
- N6: Validation errors collected across ALL sections before returning 400.

Web UI check (B3): web/src/components/pages/admin-server-config.ts sends
runtimes, harness_configs, profiles, active_profile, and workspace_path in
its PUT payload — confirming the three-way classification is required.

https://github.com/ptone/scion/issues/268

* fix(hub): admin API UI compatibility and field-clearing semantics (settings-db phase 5 fixes r2)

Address all findings from the round-2 independent review of the DB-backed
admin API (Phase 5).

B1 (BLOCKING): Empty Layer-0 structs from the web UI no longer trigger 422.
The UI's buildPayload() always sends database, broker, storage, secrets,
and message_broker as empty JSON objects. These are now detected as
zero-valued structs via reflect.DeepEqual and treated as not-present,
while any struct with a meaningful non-zero field still triggers Layer-0
rejection as designed.

N1: maskSensitiveFields now clears GitHubApp.PrivateKey and WebhookSecret
in both the DB-mode and file-mode GET paths.

N2: server.env is now extracted for Layer-0 422 rejection. auth.DevMode
false-negative documented as a Go zero-value limitation consistent with
B1's zero-struct logic. auth.DevTokenFile extraction added.

N3: slog.Error with full err added at all five silent-discard error paths
(GET x3, buildSectionDocs, maintenance marshal).

N4: server.auth.dev_token_file added to layer0Prefixes, closing the gap
where dev_token_file was not covered by the existing dev_mode/dev_token
entries (design §3.1 auth.dev_*).

N5: Dead sv variable and suppressor removed from test.

N6/N7: Presence-aware field clearing implemented for postgres PUT path.
Raw request body is parsed to distinguish OMITTED fields (keep current
DB value) from EXPLICITLY-SENT empty values ("", [], null) which CLEAR
the field. Applied to: admin_emails, user_access_mode, authorized_domains,
notification_channels, public_url, and maintenance message. File-mode
behavior untouched.

https://github.com/ptone/scion/issues/268

* feat(web): env-override warnings and settings source metadata (settings-db phase 6)

Add settings-db UI features to the admin server configuration page:

- Warning banner: when the serving node reports non-empty env_overrides,
  renders a visible warning banner listing overridden fields in
  human-readable form (via koanf key → label mapping).

- Per-field badges: each form field whose koanf key appears in
  env_overrides gets an inline "Overridden by environment on this node"
  badge so admins understand the DB value is not what this node enforces.

- Section metadata: surfaces source (db/file/default), updated_by,
  updated_at, and revision as subtle captions under each settings
  section title. Only renders when section_metadata is present in the
  GET response (postgres mode).

- Ignored keys notice: if a PUT response contains non-empty
  ignored_keys, surfaces a non-blocking notice listing which submitted
  fields were not persisted.

- Graceful degradation: all new elements are gated on the presence of
  the new response fields; file-mode/SQLite deployments render
  identically to the pre-change UI.

Koanf key → field mapping uses an explicit KOANF_KEY_LABELS constant
covering all Layer-1 section keys (access, lifecycle, telemetry,
agent_defaults, endpoints, github_app, notifications). Unmapped keys
still appear in the banner with their raw koanf path.

https://github.com/ptone/scion/issues/268

* feat(hub): settings schema endpoint and docs (settings-db phase 7)

Part A — Phase 5 review cleanups:
- N2: multi-section CAS partial-apply test (access applies, lifecycle
  conflicts → response reports both correctly)
- N3: telemetry nil-path test (nil TelemetryConfig in snapshot applied
  to response without panic)
- N4: readRawBody size limit via http.MaxBytesReader (1 MB, matches
  repo convention from integrations/harness-config handlers)
- N5: SectionMetadata struct tag alignment verified (already gofmt-clean)
- N6: parseFieldPresence error logging at all call sites — falls back to
  omitted-semantics on malformed body (typed decode will 400 anyway)
- N1/N7: zero-value limitations documented in server-config.md

Part B — Schema endpoint:
- GET /api/v1/admin/server-config/schema returns JSON-schema fragments
  and koanf paths per section from the opsettings registry
- Static metadata, no DB access, works in both postgres and file mode
- Admin auth gated, GET-only
- SchemaInfo() export added to pkg/config/opsettings/registry.go
- Tests: shape, auth gating, stable output, method not allowed

Part C — Documentation:
- server-config.md: two-tier architecture section with Layer-0/Layer-1
  key tables, precedence, seeding, env-override warnings, API behavior
  notes, zero-value limitations
- settings.yaml.example: annotated master template with all keys,
  Layer 0 vs Layer 1 markers, safe example values, no secrets

https://github.com/ptone/scion/issues/268

* fix(web): complete per-field env badges and clean up handleSave (settings-db phase 6 review fixes)

Address Phase 6 review findings:

- Add 5 missing renderEnvBadge() calls: telemetry cloud protocol,
  telemetry cloud provider, GitHub App API Base URL, GitHub App
  webhooks, GitHub App installation URL.
- Remove duplicate successMessage assignment in handleSave() (the
  first assignment before loadConfig() was never visible).
- Add KOANF_KEY_LABELS entry for server.github_app.private_key_path.

https://github.com/ptone/scion/issues/268

* test(hub): HA end-to-end settings propagation tests (settings-db phase 8)

Part A — Phase 7 review cleanups:
- N1: Remove dead code (double body assignment) in CAS partial-apply test
- N2: Add broker.cors to docs Layer-0 CORS row per design §3.1
- N3: Add concurrency comment documenting rawSchemas write-once/read-many assumption
- N4: SchemaInfo() now logs schemaCompileErr when returning nil

Part B — HA end-to-end integration tests (build tag: integration):
- AC1: PUT user_access_mode on hub A → GET on hub B returns it and B's
  auth path enforces it within ≤2s via LISTEN/NOTIFY propagation
- AC2: Concurrent first startup of N replicas seeds hub_settings exactly
  once under advisory lock; GET shows source "db" for seeded sections
- AC3: PUT maintenance on A → enforced on B; restart both hubs (fresh
  Server instances, same DB) → maintenance still enforced on both
- AC5: SCION_SERVER_* env var on one hub → that node's GET reports
  env_overrides + serves env value; other node serves DB value
- AC7: Concurrent PUTs with expected_revisions: exactly one 409 loser;
  without CAS, last-writer-wins and both writes audited (updated_by)
- AC9: Disrupted LISTEN connection (noop publisher) → settings change
  still lands via poll backstop with shortened interval
- AC10: Rollback safety — file-mode Server against DB with hub_settings
  rows boots cleanly, serves file values, ignores the table

Production code change: OperationalSettings.PollInterval field (defaults
to 60s) makes the backstop ticker injectable for AC9 testing.

https://github.com/ptone/scion/issues/268

* fix(hub): review cleanup — errors.Is, seed/reconnect comments (settings-db phase 8)

- isNotFound: use errors.Is(err, store.ErrNotFound) instead of string comparison
- seedForTest: document re-implementation rationale (lock-mechanism isolation)
- AC9 test: document that reconnect-refresh path is verified by inspection

https://github.com/ptone/scion/issues/268

* fix(lint): address golangci-lint errcheck, govet, staticcheck findings

* fix(lint): check remaining json.Unmarshal error returns

---------

Co-authored-by: Scion Agent (sdb-dev-p1) <agent@scion.dev>

* fix(store): honor cursor in ProjectStore.ListProjects (#641)

ListProjects ignored ListOptions.Cursor and never set ListResult.NextCursor,
so callers could only ever see the first (default 50-row) page — even though
the project-list HTTP handler already forwards a cursor. Add keyset pagination
ordered by (created DESC, id DESC), matching the AllowListStore/agent/message
store pattern already used elsewhere in this package.

* fix(agent): honor explicit --workspace over git auto-detection (#642)

* fix(agent): honor explicit --workspace over git auto-detection

ProvisionAgent documents and implements "an explicit --workspace overrides
everything else… mount this path directly, no worktree or branch, even if
inside a repo" (provision.go). But the mount builder in Start disagreed: it
recomputed the git repo root with no guard on opts.Workspace, so an explicit
workspace that merely happened to sit inside a git repository was widened to
the whole repository — the entire .git and every sibling directory got mounted
into the agent, and the container workdir silently moved to /repo-root/<rel>.

Extract the detection into detectRepoRoot and short-circuit it when an explicit
workspace is set, so that path is always plain-mounted in place. This makes the
mount builder agree with ProvisionAgent's contract and the workspace docs. The
no-explicit-workspace path is unchanged.

* fix(agent): honor explicit --workspace on resume and restart

The explicit-workspace guard from the previous commit only applies when
opts.Workspace is set — true on first start, but empty on resume/restart
(cmd/resume.go has no --workspace flag). Two things then broke on resume for an
explicit --workspace agent:

1. Mount widening: the explicit path is recovered into effectiveWorkspace from
   the persisted /workspace volume, so detectRepoRoot ran git detection and, when
   the workspace sat inside a repo, widened the mount back to the whole repo
   (re-mounting .git and exposing committed sibling content).

2. Mount mis-source on a git project dir: GetAgent saw the managed per-agent
   worktree absent and, when the project dir was itself a git repo,
   CreateWorktree'd a throwaway managed worktree — the agent then silently edited
   that phantom branch instead of the operator's explicit tree.

Persist the explicit-workspace intent at provision time (ScionConfig
ExplicitWorkspace) and re-derive it in Start when opts.Workspace is empty, so the
plain-mount decision holds identically on first start, resume, and restart (fixes
1). And in GetAgent, skip the managed-worktree recovery for explicit-workspace
agents by clearing agentWorkspace like a shared-workspace agent, so the explicit
path is recovered from the /workspace volume (fixes 2).

* harness: two-phase image resolution with three-entity visibility and local image management (#644)

* config: generalize RewriteImageRegistry to all short-form harness images

* cmd/build: stop overwriting image field in config on build

* agent: two-phase image resolution — prefer local short-form over registry

* imagecheck: add CheckAll() returning three-entity image status

* hub: enhanced image-status + delete-local-image + pull-image endpoints

* web: three-entity image panel with local/registry resolution visibility

* harness-image-selection: address PR #644 review — nil guards, delete existence check, loading state

---------

Co-authored-by: Scion Agent (his-dev) <agent@scion.dev>

* hub: self-healing DB←GCS sync for harness-config and template hash mismatches (#643)

* feat: auto-repair harness-config hash mismatches at dispatch time

In multi-hub deployments sharing a GCS bucket, each hub's local DB
manifest can become stale when a peer hub uploads newer harness-config
files. This causes hash-mismatch errors during broker hydration,
killing agent dispatch.

Add runtime self-healing:
- Dispatch intercept: when DispatchAgentStart/Create returns a hash
  mismatch error, sync the DB manifest from actual GCS content and
  retry the dispatch once
- Startup reconciliation: SyncAllHarnessConfigsFromStorage runs on
  boot to detect and repair stale DB manifests before any dispatch

* fix: add hash-mismatch repair+retry to DispatchAgentProvision

Missed in the initial implementation — DispatchAgentProvision also
calls client.CreateAgent and needs the same isHashMismatchError
detection, DB→GCS repair, and single-retry pattern.

* feat: generalize hash-mismatch repair to cover templates and harness-configs

Refactor the repair logic into a shared syncResourceFromStorage helper
that works for any GCS-backed resource kind. Both templates and
harness-configs now get the same self-healing treatment:

- Dispatch: repairHashMismatch parses the error prefix to route to the
  correct repairer (template vs harness-config), falling back to trying
  both when the prefix is unrecognized
- Startup: SyncAllTemplatesFromStorage runs alongside
  SyncAllHarnessConfigsFromStorage to reconcile stale DB manifests
- Server: template repairer wired via SetTemplateRepairer in
  CreateAuthenticatedDispatcher

* hub: address PR #643 review — nil guards, error propagation, singleflight dedup

HIGH: Add AppliedConfig nil guards in repairHarnessConfig and
repairTemplate to prevent nil pointer panics.

MEDIUM: Propagate GetObject errors (only skip ErrNotFound, fail on
others). Propagate computeStoredHash errors instead of silently
skipping. Add singleflight.Group to deduplicate concurrent repair
attempts for the same resource. Add nil checks for rs, report, and
list results in startup sync and lookup helpers.

---------

Co-authored-by: Scion Agent (hash-repair-dev) <agent@scion.dev>

* feat: show image pull progress (X of N) in onboarding wizard (#645)

* feat: show image pull progress (X of N) in onboarding wizard

Add Index and Total fields to PullResult so the frontend knows which
image is being pulled and how many there are in total. The onboarding
SPA uses these to render a progress bar and "Pulling image X of N..."
summary above the per-image status list during an active pull.

* Update pkg/runtime/imagepull.go

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update web/src/components/pages/onboarding.ts

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: Scion <scion@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* changelog: add 2026-07-06 and 2026-07-07 entries (#648)

* changelog: add 2026-07-06 entry

Harnesslib consolidation (-3376 lines), onboarding wizard rework,
Gemini CLI vertex-ai auth fix, docs-site refresh.

* changelog: add 2026-07-07 entry

Chat interrupt sequence, macOS runtime detection fixes, daemon
process isolation, harness-config archival, sciontool stderr fix.

---------

Co-authored-by: Scion Agent (changelog-daily) <agent@scion.dev>

* docs: add Skill Bank and harness lifecycle documentation (#653)

* docs: document the Skill Bank (authoring, publishing, registry & federation)

Add guide coverage for the previously-undocumented Skill Bank (P1):

- local/skills.md: skill file format (SKILL.md), scion skills CLI,
  skill reference URIs, scopes & resolution order, semver publishing,
  content-hash caching, platform/workspace auto-injected skills.
- hosted/single-node/skill-registry.md: Hub registry storage model,
  federation (gh://, gcp-skill://, external registries), trust/pinning,
  scion skills registries CLI, and the skills web UI.
- templates.md: cross-reference the new Skill Bank pages from the
  template-mounted §Skills section.
- reference/cli.md: add the scion skills / scion skills registries group.
- glossary (docs + root GLOSSARY.md): add Skill Bank, Skill Registry,
  Skill Reference URI.
- astro.config.mjs: add both pages to the sidebar (Local Mode + hosted
  single-node/HA Admin Guides).

Verified with 'npm run build' (Node 22 + D2): all internal links valid.

Item of concern: skills IA spans local/ and hosted/ per the brief; the
mode-axis structure has no single home for this cross-cutting feature.

* docs(harnesses): add Copilot, Hermes, Antigravity; note container-script-only provisioning

- Add sections 5-7 for GitHub Copilot CLI, Hermes Agent, and Antigravity
  harnesses, grounded in harnesses/<name>/config.yaml + provision.py.
- Add columns for the three harnesses to the Feature Capability Matrix;
  introduce a partial (◐) marker for system-prompt-override via instruction
  projection (none of the three have a native system-prompt flag).
- Add a note that all harnesses now use container-script provisioning
  (builtin Go harnesses removed, #600).
- Disambiguate the antigravity container-script harness from the
  antigravity-preview managed-agent base agent (managed-agents.md).

* docs(harness-settings): document harness-config lifecycle management

Add a 'Managing Harness-Configs' section covering the shipped-but-undocumented
lifecycle: name field, install from source, source_url tracking + 'Refresh from
Source' (scion harness-config update --url/--all), image status, publish/pull,
list/show/reset/upgrade, and delete.

Note of concern (routed to coordinator): 'image_status' is an enum
(unknown/valid/invalid/error), NOT 'local/remote' as phrased in the brief; the
Local/Remote badge is a separate web-UI heuristic derived from the image string.
Documented both accurately per pkg/store/models.go and web/harness-config-detail.ts.

* docs(agent-credentials): document container-script auth model

- Describe the container-script (provision.py) provisioning model and the
  host-gather / in-container-select split (scion_harness library).
- Correct the source precedence: per-key staged-candidate/secret -> env ->
  well-known file, with vertex-ai/GCP-metadata as an auth *type*. (Brief's
  'keyring > file > env > GCP metadata' is not a single chain in code; see
  pkg/harness/auth.go and harnesses/scion_harness.py.)
- Add No-Auth Fallback, Capturing Credentials, Repairing Auth (scion reset-auth),
  and Diagnostics (scion doctor / sciontool doctor) sections.

Note of concern (routed to coordinator): there is NO 'scion auth capture' CLI
command. Capture is 'python3 ~/.scion/harness/capture_auth.py' run in-container,
which delegates to 'sciontool secret set'. Documented per code and flagged the
absent wrapper.

* docs(cli): add harness-config group, reset-auth, and doctor commands

- Add a 'Harness Configuration' section for the scion harness-config (hc) group:
  list, show, install, update (--url/--all), sync/push, pull, reset, upgrade, delete.
- Add scion reset-auth (repair-inject token into a running agent).
- Add scion doctor (host diagnostics) with a note on the in-container
  sciontool doctor. Verified against cmd/ (reset_auth.go, doctor.go,
  harness_config*.go).

* docs: address PR #653 review feedback

- supported-harnesses.md: fix 'Interupt' -> 'Interrupt' typo in capability table
- supported-harnesses.md: clarify system-prompt override footnote (AGENTS.md=Hermes,
  GEMINI.md=Antigravity, copilot-instructions.md=Copilot; gemini has full support
  via ~/.gemini/system_prompt.md)
- reference/cli.md: drop redundant 'Additively' from upgrade description
- reference/harness-settings.md: drop redundant 'additively' from upgrade comment
- hosted/single-node/skill-registry.md: rephrase GITHUB_TOKEN guidance to
  'provide ... in the agent environment'
- local/skills.md: clarify gitignore exclusion ('files matching .gitignore patterns')

* ci: retrigger typecheck

* fix(web): remove unused declarations in harness-config-detail.ts

---------

Co-authored-by: Scion Agent (du-skills) <agent@scion.dev>
Co-authored-by: Scion Agent <scion-agent@users.noreply.github.com>
Co-authored-by: Scion Agent <scion-agent@scion.dev>

* fix(web): remove unused resolvedImage and isRemoteImage from harness-config-detail (#654)

Co-authored-by: Scion Agent (fix-ts-unused) <agent@scion.dev>

* style: gofmt pkg/hub/imagecheck/checker.go (#658)

Co-authored-by: Scion Agent <scion-agent@users.noreply.github.com>

* fix(server): honor explicit --dev-auth=false in workstation daemon mode (#652)

* fix(server): honor explicit --dev-auth=false in workstation daemon mode

`scion server start` re-exec's itself as a `--foreground` child. The daemon
arg builder forwarded workstation-defaulted bool flags only when true
(`if enableDevAuth { append("--dev-auth") }`), so an explicit `--dev-auth=false`
was dropped from the child's argv. The child then re-ran
applyWorkstationDefaults, which re-enables any such flag it does not see as
explicitly set — silently turning dev-auth back on. `scion server start
--dev-auth=false` therefore started with dev-auth ENABLED (auto-generating a
dev token), with no way to disable it in workstation daemon mode.

Forward explicitly-set workstation-defaulted bools as `--flag=<value>` via a
new appendDaemonBoolFlag helper, so a disable survives into the child (which
then sees the flag as changed and does not re-apply the workstation default).
Applies to --enable-hub, --enable-runtime-broker, --enable-web, --dev-auth and
--auto-provide, in both the initial daemon builder and the restart
reconstruct-from-flags fallback. Unset flags keep the historical bare form.
Adds a regression test for the arg builder.

* fix(server): keep restart fallback as-is; document its pre-existing limitation

Addresses review on the initial commit. The restart fallback in
runServerRestart used appendDaemonBoolFlag with serverRestartCmd, which does
not register --enable-hub/-runtime-broker/-web/--dev-auth (those live on
serverStartCmd) and never populates the corresponding globals — so
cmd.Flags().Changed() is always false there and the helper is inert. That was
already true of the original code (the fallback reduced to
["server","start","--foreground"]), so this is a pre-existing limitation, not a
regression. Revert the restart hunk to its original form and document the
limitation instead of pretending to fix it here.

The daemon-start fix (appendDaemonBoolFlag in runServerStart) is unaffected:
its corrected args are persisted via SaveArgs and reloaded by the normal
restart path. Properly reconstructing flags in the restart fallback is left to
a separate change.

* fix(server): forward remaining explicitly-set start flags to the daemon child

Extend the daemon-arg builder so every flag registered on serverStartCmd that
is set explicitly survives the re-exec into the --foreground child, not just the
workstation-defaulted bools. Adds --no-auto-migrate, --enable-test-login and
--simulate-remote-broker (via appendDaemonBoolFlag) plus --template-cache-dir,
--template-cache-max, --web-assets-dir, --session-secret, --base-url and
--admin-emails (forwarded when Changed). Previously these were dropped in daemon
mode and the child fell back to defaults (e.g. --session-secret was regenerated,
--base-url/--admin-emails lost).

Extract the argv construction into buildDaemonStartArgs and add a test covering
the explicit-disable and the newly-forwarded flags.

* fix(server): forward --hosted correctly, guard --host, drop --session-secret

Addresses the second review round on this PR:

- --hosted is now forwarded via appendDaemonBoolFlag so an explicit
  --hosted=false survives the re-exec — a child with mode:"hosted" in config no
  longer flips back to hosted and overrides the user's workstation choice.
- --host is forwarded only when explicitly set (Changed). Forwarding the parent
  default unconditionally made the child treat --host as changed, clobbering a
  config-file host and skipping the workstation loopback default for the runtime
  broker.
- --session-secret is no longer forwarded. It is a signing secret; the daemon
  argv is visible in the process list and persisted (0644) to server-args.json
  via SaveArgs, so forwarding it exposed the secret. A stable session secret in
  daemon mode should be supplied out-of-band. Documented with a NOTE.

Extends the buildDaemonStartArgs test to cover all three (hosted=false
forwarded; host omitted when unset, forwarded when explicitly set; session
secret never present and its value never leaking into the args).

* hub: drop GCS-absent files from manifest in syncResourceFromStorage (#656)

* fix: exclude GCS-missing files from repaired manifest in syncResourceFromStorage

When a file listed in the DB manifest returns ErrNotFound from GCS,
the repair was logging a warning but still including the missing file
in the rebuilt manifest. The broker would then 404 on those files
during dispatch.

Switch from copy-then-mutate to build-as-you-go: only append files
that actually exist in GCS. Missing files are dropped with a warning
log, consistent with GCS-as-truth semantics.

* style: gofmt pkg/hub/imagecheck/checker.go

---------

Co-authored-by: Scion Agent (hash-repair-dev) <agent@scion.dev>
Co-authored-by: Scion Agent (ci-fmt-656) <scion-agent@scion.dev>

* chore(harness): bump antigravity to version 1.1.0 (#659)

Co-authored-by: Scion Agent <scion-agent@scion.dev>

* feat(harness/antigravity): add model range with thinking-level bucket mapping (#660)

Add model_aliases (small/medium→Flash, large/extra-large→Pro) to
config.yaml and implement thinking-level bucket mapping in provision.py.

The provisioner now reads the model from the manifest harness config
(with AGY_MODEL env var fallback) and maps AGY_THINKING_LEVEL (0-100,
default 50) to named buckets:
- Flash: 0-33→low, 34-66→medium, 67-100→high
- Pro: 0-49→low, 50-100→high

Both model and thinkingBudget are written to AGY's settings.json.

Co-authored-by: Scion Agent (antigravity-models-dev) <agent@scion.dev>

* config: add thinking_budget fields to harness config schema (#661)

The HarnessConfigData Go struct has ThinkingBudgetMap, ThinkingBudgetFlag,
and ThinkingBudgetConfigKey fields, but the JSON schema was missing them.
Since the schema uses additionalProperties: false, any harness config.yaml
containing these fields would fail validation.

Co-authored-by: Scion Agent (schema-fix-dev) <agent@scion.dev>

* agent: thinking level field + model choice UX refactor (#662)

* feat(web,hub): add thinking level UX and remove model from agent create

Remove model choice from agent creation form — model is now an
agent-level setting changed after creation. Update model dropdown
placeholder to "use harness default" in agent edit and project
defaults forms.

Add a 0-100 thinking level slider to agent edit and project defaults.
The slider uses a checkbox toggle so unset means "use harness default"
rather than defaulting to a value in the UI.

Add ThinkingLevel *int field to ScionConfig, AgentAppliedConfig, and
ProjectSettings. Wire through create, update, and project defaults
apply paths so the value flows from UI to harness provisioner.

* fix: add ThinkingLevel validation and fix unset semantics

Add server-side range validation (0-100) for ThinkingLevel in agent
create, agent update, and project settings handlers, returning 400
if out of range.

Fix unset semantics in agent update: the frontend now always sends
thinking_level (null or number) and the backend unconditionally
applies it from the config, so explicitly setting null correctly
clears the value instead of silently preserving the old one.

---------

Co-authored-by: Scion Agent (thinking-level-ux-dev) <agent@scion.dev>

* fix(web): correct AgentWithConfig interface extension (#664)

Use Omit<Agent, 'appliedConfig'> to avoid TS2430 — the local
AppliedConfig has a richer inlineConfig shape than the shared
AgentAppliedConfig, making a plain extends incompatible.

Co-authored-by: Scion Agent <scion-agent@users.noreply.github.com>

* fix: integration config architecture fixes (R0-R3) (#663)

* fix: propagate config_file into plugin manager config map (R0)

config_file was stored in PluginEntry.ConfigFile but never copied into
the Config map[string]string that flows through DiscoverPlugins and
GetPluginConfig. This caused handleUpdateIntegrationConfig to always
return 400 "integration has no config file configured" for Mode 1/2
integrations, and reconfigureIntegration to skip YAML re-reads.

Insert config_file into the Config map at three propagation points:
- initPluginManager (server_foreground.go) after LoadPluginConfigFile
- DiscoverPlugins (discovery.go) before building DiscoveredPlugin
- LoadOne (manager.go) before dispatching to loadPlugin/loadGRPCPlugin

Add end-to-end and unit tests verifying config_file survives the full
DiscoverPlugins → Manager.GetPluginConfig chain.

* fix: gate hub config pushes for HA integrations and read GET from correct provider (R1+R2)

R1: Skip reconfigureIntegration after HA config saves — the DB write +
NOTIFY is the reconfigure path, so pushing a hub-side merge over gRPC
races with the DB-backed reload. Also gate boot credential injection
to exclude HA adapters, which pull credentials from env/Secret Manager.

R2: handleGetIntegration now resolves settings from the same provider
that PUT writes to (Postgres for HA, YAML file for non-HA) instead of
always returning the stale boot-time manager map.

* fix: unify config precedence — file wins over inline, backend secrets win over YAML (R3)

* fix: pass config_file through InstallPlugin to close UI-install hole (R0-F1)

* fix: auto-migrate inline secrets at boot and use runtime-key allowlist in reconfigure (R3-B1/B2/B3)

* fix: add ReplaceBrokerConfig for reconfigure and fix gofmt (review blockers)

- Add Manager.ReplaceBrokerConfig that updates stored config and sends
  exact cfg to the plugin with no underlay from boot-time dp.Config,
  preventing deleted config keys from being resurrected on reconfigure.
- Switch reconfigureIntegration to use ReplaceBrokerConfig instead of
  ConfigureBroker, and add wiring keys (mode, path, address, tls_*)
  to the runtimeKeys allowlist so they survive replace semantics.
- Fix gofmt alignment in the runtimeKeys map literal.
- Fix SecretType in migrateInlineSecrets to use secret.TypeVariable
  with InjectionMode "as_needed", matching SetChatIntegrationSecret.

* fix: address 4 PR #663 review comments

1. reconfigureIntegration: pass pluginCfg as inline config when no
   config file is set, so inline-only deployments retain their keys.
2. resolveIntegrationSettings: use ResolvePluginConfig instead of the
   deprecated LoadPluginConfigFile.
3. Move runtimeKeys map to package-level var to avoid allocation on
   every reconfigureIntegration call.
4. migrateInlineSecrets: check sb.Get error before proceeding — log
   and skip on transient errors instead of potentially overwriting a
   valid backend secret.

---------

Co-authored-by: Scion Agent (ca-dev-r0) <agent@scion.dev>

* fix(web): add syntax highlighting for JSON and YAML in workspace file viewer (#665)

* fix(web): add syntax highlighting for JSON and YAML in workspace file viewer

JSON and YAML files were opened in a raw browser tab when clicking the
preview (eye) icon, losing all formatting. Route them through the inline
editor component instead — the same path Markdown already uses — so they
get CodeMirror syntax highlighting in read-only mode.

* ci: retrigger

---------

Co-authored-by: Scion Agent (fix-201) <scion-agent@users.noreply.github.com>
Co-authored-by: Scion Agent <scion-agent@scion.dev>

* feat(ha): introduce hub_name as Layer-1 operational setting, replace hostname-based hub identity (#667)

* design: hub scope — hub_name for HA multi-node deployments

Add design document for #392: replacing hostname-based hub identification
with a configurable hub_name (Layer-1 setting) for HA deployments.

Covers: current state analysis, settings integration, migration strategy,
phased implementation plan, and open questions.

* design: update hub-scope for two-identifier model refinement

Incorporate user direction on hub_id flexibility:
- hub_id (Layer-0) now accepts any unique string slug, not just hex
- hub_name (Layer-1) is the new human-readable display name
- Added section 2b documenting hub_id flexibility changes
- Updated Phase 1 to include hub_id schema relaxation
- Resolved open questions per user decisions

* feat(config): add hub_name setting and relax hub_id format

Add hub_name as a Layer-1 operational setting for human-readable hub
identification in HA deployments. Relax hub_id to accept any string
slug (not just hex). No runtime behavior changes.

Part of #392.

* feat(hub): wire hub_name through server config and plumbing

Make hub_name available throughout the hub server via ServerConfig,
ApplySnapshot, and accessor methods. Prepare hubmetrics and secret
backend to accept hub_name. No surface area changes yet.

Part of #392.

* fix(config): allow single-character hub_name in schema pattern

Adjust regex to ^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$ so single-char
names like "a" are valid, matching the design doc's 1-63 char spec.

Part of #392.

* feat(hub): migrate surface area from hostname to hub_name

Replace os.Hostname() with hub_name in all hub-level identity sites:
- Log labels: "hub" now uses hub_name, added "node" for hostname
- Telemetry: added scion.hub.name resource attribute
- GCP secrets: replaced scion-hub-hostname with scion-hub-name label
- Admin API: include hub_id and hub_name in health responses

Part of #392.

* feat(chat-app): update to use hub_name label for secret discovery

Update the chat app to filter secrets by scion-hub-name instead of
scion-hub-hostname. Support SCION_HUB_NAME env var with hostname
fallback. Mark design doc as Implemented.

Part of #392.

* fix: address Phase 3+4 review findings (R2, R3)

Add test for explicit hub_name in GCP secret labels.
Update design doc to note deferred scion-hub-node label.

Part of #392.

* fix: wire hub_name to GCP secret backend and fix chat app label compat

P1: Wire SetHubName in production — after NewBackend() returns in
server_foreground.go, type-assert to *GCPBackend and call
SetHubName(cfg.Hub.ResolveHubName()) so the hub_name config value
reaches the GCP backend for label creation.

P2: Fix chat app label filter — update discoverSigningKey to query
with an OR filter (labels.scion-hub-name=X OR labels.scion-hub-hostname=X)
so existing secrets with the old label are still discoverable after upgrade.

P3: Propagate runtime hub_name changes — in ApplySnapshot, after
updating s.config.HubName, propagate to the GCPBackend via SetHubName
so runtime admin API changes take effect on new secret labels.

Update design doc §4.4 to note the chat app exception: labels ARE used
functionally by the chat app for secret discovery, not just for GCP
Console filtering.

* fix: address final review findings (data race, env vars, label cleanup)

- Add sync.RWMutex to GCPBackend protecting hubName against concurrent
  access from SetHubName (ApplySnapshot) and resolveHubName (Set)
- Accept SCION_SERVER_HUB_HUBNAME as fallback in resolveHubNameFromEnv
  so operators using either env var get consistent early logging
- Remove redundant labels["hostname"] from GCPHandler for consistency
  with cloud_handler and message_log (only "node" is emitted)
- Add TestGCPHandler_HubNameLabel verifying non-empty hub name in labels

* fix: address PR #667 review feedback on env var precedence and regex

- Swap SCION_SERVER_HUB_HUBNAME / SCION_HUB_NAME check order in
  resolveHubNameFromEnv so the koanf-derived name takes precedence
- Add SCION_SERVER_HUB_HUBNAME support to chat app's discoverSigningKey
  with the same precedence order
- Update design doc regex to match implementation: allow single-char
  hub names with ^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$

---------

Co-authored-by: Scion Agent (hs-arch) <agent@scion.dev>

* feat(brokers): add broker type labels and human-readable names (#666)

* design: add broker label and type display design doc

Design document for surfacing broker type (hosted/external) via labels
and replacing the opaque Cloud Run hostname with "Hosted Broker".
All 4 open design questions resolved with project owner.

* feat(brokers): add broker type labels and display badges

- Remove vestigial `type` column from RuntimeBroker ent schema
- Override co-located broker name to "Hosted Broker" when no explicit
  nickname is configured (replaces opaque os.Hostname() fallback)
- Set `scion.io/broker-type: hosted` label on co-located brokers
  (new and backfilled on existing brokers at startup)
- Default `scion.io/broker-type: external` label on new external
  broker registrations (overridable via request labels)
- Add `labels` field to frontend RuntimeBroker TypeScript interface
- Render "Hosted"/"External" type badges in brokers list (grid and
  table views) and broker detail header

* refactor: address review feedback on broker labels

1. Add comment to server_broker.go explaining why the unconditional
   label overwrite is safe (function is only called for co-located
   brokers; external brokers go through brokerauth.go).

2. Extract .broker-type-badge CSS into shared brokerTypeBadgeStyles
   in resource-styles.ts, imported by both brokers.ts and
   broker-detail.ts — eliminates the duplication.

3. go.sum: olekukonko/tablewriter v0.0.5 is a transitive dependency
   pulled in by ent's code generator during go generate — expected.

---------

Co-authored-by: Scion Agent (bl-arch) <agent@scion.dev>

* fix: treat ErrNotFound as safe-to-create in migrateInlineSecrets (#407) (#668)

* fix: treat ErrNotFound as safe-to-create in migrateInlineSecrets (#407)

Two fixes for inline secret migration during plugin initialization:

1. migrateInlineSecrets: When the secret backend returns ErrNotFound
   (secret doesn't exist yet), treat it as safe-to-create instead of
   skipping migration. Only skip on real errors.

2. initPluginManager: Inject secrets from the backend into the merged
   plugin config before LoadAll calls Configure. Without this, plugins
   that validate required keys during Configure (e.g. Telegram's
   bot_token) fail because ResolvePluginConfig already stripped inline
   secrets and injectPluginSecrets runs too late.

* fix: handle nil mergedConfig in injectPluginSecretsIntoConfig

Return the (possibly newly allocated) map from
injectPluginSecretsIntoConfig so that plugins with no config block in
settings.yaml can still receive injected secrets.

Addresses PR #668 review comments.

---------

Co-authored-by: Scion Agent (ca-fix-407) <agent@scion.dev>

* fix(ui): filter message brokers from /brokers page, show only runtime brokers (#669)

* fix(ui): filter message brokers from /brokers page, show only runtime brokers

* Update pkg/hub/handlers_runtime_brokers.go

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: Scion Agent (fix-393) <agent@scion.dev>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* feat(storage): namespace GCS paths with hub ID for HA multi-tenant isolation (#670)

* harness/gemini-cli: update model aliases and fix auth.selectedType in settings.json (#671)

* harness/gemini-cli: update model aliases and fix auth.selectedType in settings.json

Update model_aliases in config.yaml:
- medium: gemini-flash → gemini-3.5-flash
- large: gemini-pro → gemini-3.1-pro-preview

Verified auth.selectedType is already written correctly by
_update_gemini_settings() in provision.py via _GEMINI_AUTH_TYPE_MAP.

* harness/gemini-cli: update model aliases and fix auth.selectedType in settings.json

Update model_aliases in config.yaml:
- medium: gemini-flash → gemini-3.5-flash
- large: gemini-pro → gemini-3.1-pro-preview

Fix auth.selectedType not written when auth resolves to 'none':
- Remove the if-guard in provision() so _update_gemini_settings() always
  runs, including the no-auth path.
- In _update_gemini_settings(), when gemini_auth_type is empty (no auth),
  remove selectedType and clean up the empty auth dict from security
  rather than leaving the seed's bare 'auth: {}' untouched.

---------

Co-authored-by: Scion Agent (gemini-cli-fixes-dev) <agent@scion.dev>

* Update image name in gemini-cli config

* fix(broker): make co-located heartbeat respect control channel state (#672)

* harness/gemini-cli: fix system instruction provisioning (#676)

The container-side provision.py only handled auth — it never read the
staged system prompt or agent instructions from .scion/harness/inputs/
and wrote them to their target locations.  Other harnesses (hermes,
copilot, codex) all call scion_harness.project_instructions() for this;
gemini-cli was missing it entirely.

Two fixes:
1. Add _apply_native_system_prompt() to read the staged system-prompt.md
   and write it to .gemini/system_prompt.md (the native location declared
   in config.yaml with system_prompt_mode: native).
2. Call project_instructions() with system_prompt_mode="none" to write
   agent instructions and skills to .gemini/GEMINI.md without duplicating
   the system prompt (already written natively).
3. Move the seed system_prompt.md from home/ to home/.gemini/ to match
   the declared system_prompt_file path.

Co-authored-by: Scion Agent (gemini-system-prompt-dev) <agent@scion.dev>

* fix(cli): auto-query Hub for harness-config list in agent containers (#675)

When running inside a hub-connected agent container (detected via
SCION_HUB_ENDPOINT/SCION_HUB_URL env vars), automatically include
Hub harness configs without requiring the --hub flag. Previously,
the command only checked the local filesystem, which is empty in
containers, resulting in "No harness configurations found."

Co-authored-by: Scion Agent (harness-config-list-dev) <agent@scion.dev>

* harness/claude: fix workspace trust dialog + add project_instructions() (#677)

* harness/claude: fix workspace trust dialog for git-clone-per-agent

For git-clone-per-agent agents, ctx.workspace points to a nested agent
directory (e.g. /workspace/.scion/grove-configs/.../agents/bbb/workspace)
but Claude Code is launched in /workspace. The provisioned .claude.json
only had a trust entry for the agent-specific path, so the trust dialog
fired every time.

Fix: pre-trust /workspace alongside the agent workspace when they differ.
Also detect the installed Claude Code version at provision time and set
lastReleaseNotesSeen/lastOnboardingVersion so release-notes and onboarding
dialogs don't fire. Update the seed .claude.json to a recent baseline
version (2.1.197) and add a /workspace project entry.

Fixes #178 (recurrence).

* harness/claude: add project_instructions() call to provision.py

The claude harness provision.py did not call
scion_harness.project_instructions(), so agent template instructions
and skills were never written to CLAUDE.md during provisioning.
Every other harness (hermes, copilot, codex, gemini-cli) already
calls this.

Add the call with system_prompt_mode='none' since Claude Code handles
the system prompt natively via --system-prompt CLI flag, so it should
not be duplicated into CLAUDE.md.

Fixes #414.

* harness/claude: remove /workspace from seed .claude.json projects

_update_project_paths() picks the first project entry as the settings
template. Having /workspace first (with only 2 fields) caused agent
workspaces to get the minimal structure instead of the full template
with allowedTools, mcpServers, hasClaudeMdExternalIncludesApproved, etc.

/workspace is already added dynamically during provision, so the seed
entry was redundant and harmful.

* Update harnesses/claude/provision.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: Scion Agent (claude-harness-fix-dev) <agent@scion.dev>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* fix(auth): honor SCION_HUB_TOKEN in getHubAccessToken (#674)

* feat(broker): add deployment-type labels to broker registration (#673)

* harness/claude: fix workspace trust dialog + add project_instructions() (#678)

* fix(image-build): copy proto/ into scion-base builder stage (#680)

* provision: prevent root-owned __pycache__ from persisting after delete (#681)

* harness: apply system_prompt_flag in ContainerScriptHarness.GetCommand (#682)

* fix(server): skip container runtime probe in hosted mode (#683)

* fix(config): add camelCase koanf field support for SCION_SERVER_ env vars (#684)

* fix(store): make Ent Schema.Create idempotent on Postgres (skip 42P07 errors) (#685)

---------

Co-authored-by: Preston Holmes <ptone@google.com>
Co-authored-by: Scion Agent (mu-dev-p0) <agent@scion.dev>
Co-authored-by: Stephen Ierodiaconou <stevegeek@gmail.com>
Co-authored-by: Scion <scion@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Scion Agent <scion-agent@users.noreply.github.com>
Co-authored-by: Scion Agent <scion-agent@scion.dev>
Co-authored-by: kylemoschetto <kyle.moschetto@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant