Skip to content

chore(ci): extend Discord watch rotation schedule - #14

Closed
github-actions[bot] wants to merge 52 commits into
mainfrom
rotation-schedule-20260801
Closed

chore(ci): extend Discord watch rotation schedule#14
github-actions[bot] wants to merge 52 commits into
mainfrom
rotation-schedule-20260801

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Automated monthly housekeeping: pruned elapsed dates and extended rotation_schedule.json ~3 months out. Generated by the discord-watch-rotation-maintain workflow.

appletechie and others added 30 commits July 22, 2026 00:14
…nches

Picking a specific ACP agent (e.g. acp:droid) via a per-session harness
override silently launched whichever agent was listed first in the acp:
config block. The server canonicalized acp:<slug> to bare acp before
persisting the override, dropping the slug; the runner's _build_acp_spawn_env
then read the slug only from the bundle agent's own spec (which carries no
slug), so it fell back to acp_agents()[0].

Preserve the concrete acp:<slug> through _validated_harness_override, and
thread the raw override into _build_acp_spawn_env via a new override_harness
param (forwarded through _build_spawn_env_from_spec at both override call
sites) so the override wins over the bundle spec's harness. Non-acp overrides
and the no-override spec path are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generic-ACP agents (acp:<slug>) fell back to the generic bot icon in the
agent picker because iconForAgent had no branch for them. Add DroidIcon and
GrokIcon glyph components and map them in iconForAgent off the acp:<slug>
harness id, so a pinned droid/grok agent renders with its own mark like the
native harnesses do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ACP agents (droid, grok) advertise a SessionModelState in their session/new
response (availableModels + currentModelId) and support session/set_model, but
run_turn ignored the per-request config.model the adapter threads from the
/model override — so switching a model on an ACP session forced a subprocess
respawn instead of a live switch, and droid ignores a model in session/new
anyway.

Capture the models from session/new, expose available_models() /
current_model_id() for the picker, and apply the /model override live via
session/set_model before session/prompt when the agent offers it and the model
actually changed. A /model on a droid session now switches without a respawn;
agents that advertise no models are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ACP agents (droid, grok) advertise a live model list (session/new
SessionModelState) and support session/set_model, but omnigent surfaced no
picker — the model-picker UI is gated on the native-agent wrapper label, which
ACP sessions don't have.

Surface the list end to end:
- Executor ABC gains available_models() / current_model_id() (default empty);
  AcpExecutor already captures them from session/new.
- The harness scaffold exposes GET /v1/sessions/{id}/model-options; the
  ExecutorAdapter answers it from the inner executor.
- The runner's model-options route gains an `acp` branch that queries the
  running harness (via a new non-spawning process_manager.existing_client).
- ACP sessions get an `acp-native-ui` wrapper label at create, mapped to the
  model-options fetch; the web adds an `acp` picker kind that reads the live
  list (like codex/opencode) and rides a pick as model_override — applied live
  by the executor's session/set_model.

Empty until the first turn creates the agent session, matching the codex path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ACP model-options branch keyed on _session_harness_name, which returns the
cached bundle spec's harness — for a session started via a harness override
(e.g. Polly overridden to acp:droid) that's the bundle agent's harness
(claude-sdk), not the picked acp one. So the model picker stayed empty for the
common override path.

Also key off the running subprocess's harness (new process_manager.running_harness),
which is the actually-picked one, so an acp override is recognized.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web model picker keys on id/displayName, but ACP agents return
modelId/name — so the options rendered with empty ids. Map them in the runner's
ACP model-options branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grok Build (`grok`) had no first-class harness — only usable as a custom `acp:`
agent or as `xai/grok-*` behind openai-agents (issue omnigent-ai#2881). Add `harness: grok`
(alias `grok-build`) driving `grok agent stdio` via the generic AcpExecutor, the
same reuse pattern the issue suggests (like qwen/goose).

- inner/grok_harness.py: thin create_app wrapping AcpExecutor with a fixed
  `grok agent stdio` command; auth is Grok's own (grok login / XAI_API_KEY).
- Registry: valid_harnesses / harness_modules / alias grok-build / capabilities
  (ACP profile) / label "Grok Build" / HARNESS_GROK_MODEL.
- Install spec (curl x.ai/cli/install.sh, grok login --device-auth) + readiness
  gating on the grok binary.
- Model picker: the runner model-options branch and the acp-native-ui wrapper
  label recognize `grok`, so a native grok session gets the live model picker
  built for ACP agents.

Follow-up: an `omnigent grok` terminal wrapper (native TUI launch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The REPL /model readout ran describe_active_credential for the session's
harness. ACP agents (acp:<slug>, grok) own their own auth and model, so there
is no Omnigent provider — it fell back to the fleet default and reported e.g.
gpt-5.5 for a droid session running claude-sonnet-5, which is misleading.

Report the session's own model for ACP-based harnesses instead, noting the
agent owns its model and that /model <name> switches it via the agent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gives Grok a persistent picker entry running the already-shipped ACP grok
harness (full ACP UX: streaming, policy+consent-gated tools, live /model).
Lands in the "Agents" group, not the native-TUI row — that gate is
isNativeCodingAgent, which an ACP harness is deliberately not in, and a
native-TUI grok would require a bespoke TUI screen-scraper for zero
capability gain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auto-created terminal REPL resumes a session with a one-shot
client.sessions.get(); a transient Cloudflare 502 (origin briefly draining
after a deploy) killed the whole attach and showed a permanent "Failed to
resume" until the tab was reopened, while the SSE-based chat reconnected fine.
Bounded retry (4 attempts, 0.5s backoff) on 5xx; a 4xx (bad session id) still
fails loud on the first try.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…llama

test_build_startup_header_creds_line_hints_first_available asserts the openai
surface with no default falls back to a configured Databricks workspace. On a
dev machine running a local Ollama, ambient detection (a hardcoded
localhost:11434 TCP probe) injects an openai-serving provider that outranks
Databricks, so the creds line read "Codex → Ollama" and the test failed —
while CI (no Ollama) passed. Pin detect_providers to none so the test
exercises config-order fallback deterministically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The grok harness added `grok` + `grok-build` to the configured-harness map;
test_configured_harness_map_covers_all_spellings pinned an expected_keys set
that omitted them, so it failed with both as extra items. Add them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Polly's orchestration roster was six coding workers (claude_code, codex,
opencode, cursor, hermes, pi). Cursor is unused here, so drop it; add four more
cross-vendor coders so Polly can delegate to and cross-review across a wider
vendor spread:

- grok  — Grok Build (xAI), `grok` ACP harness (pi-style headless, ask window)
- droid — Droid (Factory), `acp:droid` ACP harness (pi-style headless)
- kimi  — Kimi Code (Moonshot), `kimi-native` TUI worker
- antigravity — Antigravity (Google/Gemini), `antigravity-native` TUI worker

Updates the roster prompt (worker list, native-vs-headless split), the preflight
`command -v` set (adds grok/droid/kimi/agy, drops cursor-agent), and bumps
max_dispatches_per_turn 6 -> 9 so a fan-out can reach the full roster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mpts

antigravity-native only auto-clears its interactive gate on a headless (no-TTY)
launch. A polly sub-agent is web-attended (watchable in the Subagents panel),
so it isn't headless and sat waiting on selections the orchestrator can't
answer. Set the explicit override in its executor config so the worker runs
unattended; the omnigent blast_radius guardrail still gates the catastrophic
set (force-push, rm -rf /, hard-reset to a remote ref).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n't stall

_derive_terminal_launch_args_from_spec produces a dispatched sub-agent's
terminal_launch_args and had branches for claude/codex/cursor-native but none
for antigravity-native, so a polly antigravity worker got None and agy launched
at its default request-review mode — parking on "Requesting permission for: ..."
a TUI prompt an orchestrator can't answer. The dispatch then never completed
(agy never went idle, so the reader never emitted the completion edge and the
orchestrator never woke).

Add an antigravity-native branch mirroring claude-native's opt-in-verbatim
contract: an explicit permission_mode: bypassPermissions translates to agy's
sole pre-emptive control flag; any other/absent mode leaves agy prompting.
Runner and relay are unchanged — the flag rides the server-persisted
terminal_launch_args and build_agy_launch appends it verbatim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agy RPC reader is stream-primary: it opens StreamAgentStateUpdates, which
only carries frames minted AFTER the stream opens. When the reader binds to a
cascade whose steps are already committed — exactly what the adopt-in-place
rebind onto a TUI-minted cascade does after a /clear — it mirrors nothing. The
assistant's reply never reaches the web chat and the turn stays open forever,
while the TUI shows the answer fine.

The poll loop is the only caller of GetCascadeTrajectorySteps, and it runs only
in the `except` arm of the stream call. A silent (not failed) stream never
raises, so the fallback never fires: observed live as 1446 summary polls,
0 step fetches, 0 events posted, and no error logged.

Read the committed snapshot once at bind and run it through the same
_process_committed_step path before entering the stream. The shared state
seen-set keeps it idempotent against the stream's own delivery, and a backfill
failure is swallowed so the stream/poll paths still run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Goose is now installed + authenticated on every fleet host, so add it as a
dispatchable worker. Uses the headless `goose` ACP harness (drives `goose acp`)
rather than `goose-native`: a dispatched worker needs structured turn events,
which the ACP path emits directly, while the native TUI mirrors its transcript
out-of-band — the fragile path that stalled antigravity dispatches. Same goose
binary either way, so the preflight gate is unchanged in spirit.

Updates the roster prompt (NINE -> TEN, native-vs-headless split), the preflight
`command -v` set, and max_dispatches_per_turn 9 -> 10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
agy's connect-RPC port is discovered by running lsof against the agy pid. The
argv used a bare "lsof", resolved through the caller's PATH — but the omnigent
host daemon runs under launchd/systemd with a minimal PATH. macOS ships lsof in
/usr/sbin, which that PATH omits, so every probe raised

    FileNotFoundError: [Errno 2] No such file or directory: 'lsof'

and discovery reported "no agy connect-RPC port bound within 20s". The reader
then never bound, never polled, and mirrored nothing: an antigravity session on
a macOS host answered in its TUI while the web chat stayed empty forever.

Resolve the binary via shutil.which with absolute fallbacks (/usr/sbin, /usr/bin,
/bin) and log distinctly when it is genuinely absent. Unavailable lsof still
degrades to "no ports" rather than raising.

Note this is a DIFFERENT defect from the reader backfill fix: that one applied
where discovery succeeded, this one is why discovery never succeeded on macOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
While agy is parked it emits neither a frame nor a connect trailer, so the
stream body's `async for` blocked indefinitely. That wedge made the loop's
`stop` checkpoint unreachable (rotation could only be actuated by cancelling
the task) and meant the poll fallback — gated on an exception — could never
fire for an idle stream.

Bound each entry with a per-frame deadline. On expiry the generator is closed
and the entry ends, so the outer loop re-checks `stop` and re-opens; a real
frame is unaffected. Closes the structural hazard the backfill fix worked
around.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reader resolved agy's connect-RPC port once at bind and held it for the
life of the run. When agy restarted its language server the port moved, so
every poll raised ConnectError against the dead port and the loop retried it
forever — the session mirrored nothing and no output ever reached the web UI.

Make state.port the single source of truth: the poll loop reads it each tick
and re-resolves the cascade's current port on a refused connect, and the
rotation detector follows the same value so it cannot go blind after a rebind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
The kimi forwarder mirrored wire content but never posted an
external_session_status edge — the only native forwarder that didn't
(claude/codex/opencode/cursor all do). A kimi sub-agent therefore finished,
delivered its answer to the transcript, and left the parent waiting on it
forever: _mark_subagent_terminal_and_wake was never reached, so no result
ever landed in the parent's inbox.

kimi's wire has no turn.end row; its agent loop steps while step.end carries
finishReason 'tool_use' and stops on 'end_turn' (1:1 with turn.prompt across
every recorded session). Map that edge to external_session_status: idle,
carrying the turn's final assistant text — the runner delivers an empty
result when an idle edge forwards none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
The kimi permission-request hook minted a fresh random _omnigent_elicitation_id
on every invocation, despite the comment claiming a stable re-attach id. kimi
fires PermissionRequest fire-and-forget and re-fires after a severed long-poll
(reconnect / transient 5xx), so a re-fire created a NEW approval card and
orphaned the one the human answered: the live hook's long-poll never saw a
verdict, _request_web_approval returned None, and the keystroke was never
injected — leaving kimi parked at its own TUI approval menu forever while the
server marked the answered card resolved and told the parent 'continuing'.

Key the elicitation id on kimi's per-tool-call id (session + tool_call_id) so a
re-fired hook re-parks the same server elicitation and picks up a verdict that
landed while it was disconnected. Distinct call ids stay distinct; fall back to
a random id only when kimi omits the call id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
A headless polly kimi worker launched at kimi's default permission mode and
parked on its in-TUI approval menu — an orchestrator can't answer it, so the
dispatch stalled (the same wall that hung env-python-toolchain). Mirror the
antigravity/cursor headless-bypass contract: translate an explicit
permission_mode: bypassPermissions on a kimi-native sub-spec into kimi's
-y/--yolo auto-approve flag, and set that mode on the packaged kimi worker so
polly fan-outs run kimi with real tools unattended. Opt-in verbatim: any other
mode leaves kimi prompting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
# Conflicts:
#	omnigent/server/routes/sessions.py
#	web/src/pages/ChatPage.tsx
A scheduled task fires with no human present, but a native-terminal harness
(claude-native, codex-native, cursor-native, antigravity-native, kimi-native)
stops to ask for tool permission — Claude Code's approval menu, agy's
request-review, kimi's in-TUI menu. With nobody to answer, the terminal never
renders its input prompt and the run fails on readiness ("terminal did not
become ready within 30s"); observed on a claude-native news-digest task whose
web-search tool triggered Claude Code's approval prompt.

The fire path created the conversation without terminal_launch_args, so the
harness always launched at its prompting default. Derive the harness's
don't-prompt flag for the run by synthesizing permission_mode: bypassPermissions
into the spec and reusing _derive_terminal_launch_args_from_spec — the same
single source of truth the headless polly sub-agent path uses. omnigent's own
PreToolUse policy hook still gates every tool call; only the interactive prompt
is suppressed. Non-native (SDK) harnesses derive no args. Best-effort: any
resolution failure leaves the run at the harness default rather than blocking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
# Conflicts:
#	tests/server/scheduled/test_fire.py
Adds a fork-only workflow that builds deploy/docker/Dockerfile and pushes
ghcr.io/apple-techie/omnigent-server:{sha-<short>,deploy-latest} on every push
to deploy/acp-slug-branding, authenticating with the built-in GITHUB_TOKEN
(permissions: packages: write) so a merge/sync no longer needs a manual
docker push + personal token. Additive and namespaced to the fork, so upstream
merges never touch it. amd64-only to match the deploy host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
GitHub excludes forks from a package's Actions-access list and the source-repo
inherit path doesn't grant it either, so GITHUB_TOKEN can't push to this
account-owned package. Authenticate the push with the GHCR_PAT repo secret (a
user PAT authenticates as the account and pushes regardless). Derive the owner
from github.repository_owner for both the registry login and the image name
rather than hardcoding it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
appletechie and others added 22 commits July 26, 2026 08:13
…ss merge)

Add upstream-drift-sync.yml: daily, trial-merges upstream/main into a standing
auto/upstream-sync branch. Clean merge -> push branch + open/update a PR
(deploy <- sync) for human diff review; merging it fires deploy-image.yml.
Conflicts -> open/refresh a 'manual merge needed' issue listing the conflicted
files and touch nothing (conflict resolution needs a human in the merge, not an
approval after). Never merges to deploy on its own; runs on GITHUB_TOKEN
(same-repo branch/PR/issue ops).

Also: skip the deploy-image rebuild on .github-only pushes (paths-ignore) so a
workflow tweak doesn't republish a byte-identical image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
Add optional workflow_dispatch inputs so a throwaway branch can exercise the
merge->PR path without touching the real deploy branch; empty on schedule keeps
the deploy/acp-slug-branding + auto/upstream-sync defaults.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
…upstream

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
Creating a project against a container-deployed server failed with 405.
create_app mounts the projects router only when a project store is wired,
and the Docker entrypoint built every other store but never this one — so
POST /v1/projects was not a route at all and fell through to the SPA
catch-all (GET-only), which answers 405. The CLI server path already wires
it, so the same build worked under `omnigent server start` and failed in
the container.

Construct SqlAlchemyProjectStore from the resolved database URL and pass it
to create_app, mirroring the other stores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A host whose tunnel recycled (seen live as "no close frame received or
sent. Reconnecting") reconnected and upserted itself online, then the OLD
connection's teardown ran and unconditionally called set_offline — leaving
a live, heartbeating host stuck at status=offline. The ping loop's
heartbeat only bumps updated_at and never restores status, so the row
stayed offline (and hidden in the UI) until a later reconnect happened to
win the race.

Give HostRegistry.deregister the connection generation guard the runner
registry already uses: it removes the entry only when it is that exact
connection, and returns what it removed. The tunnel's three teardown paths
now call set_offline only when they actually removed their own connection,
so a teardown that lost the race leaves the newer tunnel alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ce one

The fire path synthesized permission_mode: bypassPermissions onto a copy of
every agent's spec before deriving terminal launch args. That did three wrong
things: it overrode a bundle that deliberately declared plan/acceptEdits, it
silenced the user's consent gate (PermissionRequest) which the runtime keeps
deliberately independent of the deployment's policy gate, and on a root host
it produced a flag Claude Code refuses outright — the CLI exits 1 before the
terminal is ready and the run fails as required_terminal_exited.

Because the synthesized value crosses the shared per-harness mapping it also
fanned out to five different vendor flags, so the breakage was never specific
to one harness.

Derive the launch args from the spec as loaded. A bundle that wants a headless
mode declares it (the recipe the agent-authoring docs already give); a bundle
that declares nothing launches at the harness default, as it did before this
path forced anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: apeltekci <andrew@peltekci.com>
Fork hygiene: every remaining fork-only product change must map to an
open upstream PR. These had none and no viable path to one, so they come
out rather than living on as permanent divergence.

- scheduled/fire.py back to upstream parity (drops the forced permission
  bypass and its follow-up). Upstream PR closed in favour of the
  maintainer's per-task permission selector; routing the spec through
  _derive_terminal_launch_args_from_spec would still grant codex-native
  and cursor-native full bypass via that helper's defaults.
- repl conversation-resume 5xx retry. Wrong layer (the SDK client has no
  transport retry, so one call site is patched and the rest stay bare),
  reinvents post_session_event_with_retry, misses 429, and leaves
  _list_all_conversation_items free to swallow the error and render an
  empty resumed conversation.
- polly roster + worker bypass config and the built-in Grok Build agent.
  These are deployment content, not product: the roster names harnesses
  upstream doesn't ship, so a packaged bundle would be broken for every
  upstream user. Both now ship via OMNIGENT_BUILTIN_AGENT_DIRS, which
  supersedes the packaged bundles by name at startup.

Signed-off-by: apeltekci <andrew@peltekci.com>
Replaces the fork's version with what was actually submitted upstream:
declines ACP harnesses at describe_active_credential rather than in the
readout (the startup header calls the resolver independently and lied the
same way), derives the predicate from the declared capability record
instead of a hardcoded list, drops the fork-only grok id, covers goose and
qwen, and no longer claims an Omnigent-side /model override reaches the
agent.

Signed-off-by: apeltekci <andrew@peltekci.com>
Follow-on to the fork-only revert: these asserted the dropped roster and
the forced permission bypass.

Signed-off-by: apeltekci <andrew@peltekci.com>
Closes the 66-commit drift. Three conflicts resolved:

- web/package-lock.json: accepted upstream's deletion. Upstream migrated web
  and electron to a root pnpm workspace, so the lockfile is superseded by
  pnpm-lock.yaml and the Dockerfile now runs `pnpm install --frozen-lockfile`
  instead of copying the npm lockfile. Deleting it without the rest of that
  migration would have broken the image build.
- harness_readiness.py: took upstream's `_installer_only_availability(GOOSE_KEY)`
  and moved the fork's grok branch onto the same helper, matching the
  convention upstream converted the other CLI-gated harnesses to.
- test_acp_spawn_env.py: additive on both sides, kept both tests.

Also fixes a silent auto-merge break: the fork's `acp:<slug>` override branch
skipped the `cfg` assignment that upstream's new `has_embedded` check reads,
so an override raised UnboundLocalError. Hoisted the assignment.

Signed-off-by: apeltekci <andrew@peltekci.com>
Merge upstream/main into deploy (66-commit drift, 3 conflicts resolved)
The previous sync landed upstream's *content* but not its *lineage*: a
`git stash` run mid-merge cleared MERGE_HEAD, so the commit came out with a
single parent and git still reported deploy as 66 behind with a merge base
predating the drift. Every future sync would have re-conflicted on the same
files.

This is the real merge. Three conflicts, all the same shape — fork adds a
hunk, upstream side empty — so HEAD's hunk is kept and upstream's other
edits to those files stay auto-merged:

- harness_install.py / harness_readiness.py: the grok harness entries
- test_acp_spawn_env.py: the ACP model-options test

Signed-off-by: apeltekci <andrew@peltekci.com>
Fork-only with no upstream path — the branded-icon PR was deliberately
scoped to grok, so this had nowhere to land. Droid agents fall back to the
generic BotIcon like every other unmapped ACP agent.

Signed-off-by: apeltekci <andrew@peltekci.com>
Reverts the picker-visibility half of upstream omnigent-ai#2143 for this deployment.
openai-agents was never removed as a harness — valid_harnesses,
harness_modules and capabilities were untouched there — only its two label
entries, so restoring them is enough to make it selectable again.

Both sources must carry it: the web merges server rows on top of its static
BRAIN_HARNESS_LABELS fallback and only ever adds, so a row missing from
either one stays missing from the picker.

Signed-off-by: apeltekci <andrew@peltekci.com>
Four conflicts. Three are our copy vs upstream's now-merged copy of the
same change (omnigent-ai#3431, merged 2026-07-29) — took upstream wholesale, since the
maintainer's version is stricter: the helper is public
(harness_owns_its_credential) and gains an early
`if canonical in _HARNESS_FAMILY: return False`.

The fourth, runner/app.py, needed a real port rather than a side:

  ours:     _build_acp_spawn_env(spec, ..., override_harness=acp_override)
  upstream: _build_acp_spawn_env(effective_spec, cwd=cwd, workdir=workdir)

Upstream introduced `effective_spec`, which bakes in the per-session model
override. Taking our side verbatim would have silently dropped that
override for every ACP session, so this keeps upstream's spec AND our
acp:<slug> threading (omnigent-ai#3057).

The openai-agents picker label survives the merge untouched in both
sources despite upstream refactoring harness_plugins.py three times
(omnigent-ai#3239, omnigent-ai#3495, omnigent-ai#3500) — different regions, no conflict.

Signed-off-by: apeltekci <andrew@peltekci.com>
…er image

The server validates a bundle's harness against valid_harnesses() at spec
parse (_omnigent_compat.py), so a bundle declaring `harness: droid` is
rejected before it ever reaches a host — droid was usable only via the CLI
on machines with the plugin installed by hand.

Installs omnigent-droid in the server-builder stage, whose /opt/venv the
runtime stage copies. Pinned to a commit so a rebuild can't silently pick up
a different plugin revision, and --no-deps because the plugin depends on an
unpinned `omnigent` that would otherwise pull core from PyPI over this
image's own editable install.

Deployment plumbing, not product divergence: the plugin lives in its own
repo on the sanctioned community-harness entry point, so nothing here
changes upstream product code.

Signed-off-by: apeltekci <andrew@peltekci.com>
…ttps

The server-builder stage is python-slim, which ships no git binary, so
uv failed the build with "Git executable not found". GitHub's source
archive needs no git and stays pinned to the same commit, so use that
instead of adding git (and a layer) to the image purely to fetch one
small package.

Signed-off-by: apeltekci <andrew@peltekci.com>
…names

Ports the upstream PR onto deploy and bumps the droid plugin pin so the
picker works for droid.

The picker route and the session marker both matched harness names ("acp",
plus a fork-only "grok" arm), so a community ACP plugin could never be
covered. Both now read the declared capability record
(IntegrationMode.ACP_SUBPROCESS) — grok stays covered because it declares
ACP_SUBPROCESS, so the hardcoded arm was redundant.

The marker also only ran for sessions created with an explicit
harness_override, so the droid builtin agent — which declares
`harness: droid` rather than being picked as an override — was never
labelled and rendered without a picker. It now falls back to the agent's
declared harness, best-effort so a spec that cannot be loaded leaves the
session unlabelled rather than failing the create.

Plugin side (omnigent-droid @ 30854927): session/new already returned a
models block that was parsed and discarded; it is now kept and exposed via
available_models() / current_model_id().

Signed-off-by: apeltekci <andrew@peltekci.com>
Plugin registers a spawn-env builder so the per-session /model override lands
in HARNESS_DROID_MODEL, and applies session/set_model per turn so a mid-session
pick is not inert.

Signed-off-by: apeltekci <andrew@peltekci.com>
An ACP agent owns its own model, so a session with no override has no
Omnigent-visible model to show. But `acp` fell through to the branch that
falls back to `selectedModel` — the global sticky — so the composer
confidently labelled a fresh droid session with a pick left over from an
unrelated session while droid was answering on its own configured model.

Same hazard the neighbouring comment already names for SDK/bundle agents;
`pickerSelectedModel` already excluded the sticky for acp, this path didn't.

Signed-off-by: apeltekci <andrew@peltekci.com>
@appletechie

Copy link
Copy Markdown
Owner

🤖 Polly Review

Review could not be completed: Omnigent session failed

appletechie referenced this pull request Aug 5, 2026
…ent-ai#4080)

Closes omnigent-ai#866

The `omnigent` Homebrew formula has not built since 0.7.0, and the tap reported
green anyway, so 0.7.0, 0.8.0 and 0.8.1 all merged with no bottle — every user
compiled from source, and CEL policies silently did not work.

- **Root cause was the CEL migration.** omnigent-ai#2970 swapped `cel-expr-python` for
  `cel-python` on the premise that it is pure Python. It is not: `cel-python`
  hard-depends on `google-re2`, whose sdist runs `bazel build` whenever
  `GITHUB_ACTIONS` is set. The bazel dependency moved rather than disappeared.
- **Pin compiled extensions to upstream wheels.** `generate_formula.py` gains
  `WHEEL_REQUIRED` / `PREFER_WHEEL` / `PURE_WHEEL` with abi3 and universal2
  handling, so grpcio (by far the most expensive build), protobuf, regex,
  uvloop, httptools, argon2-cffi-bindings, markupsafe, pyyaml, zstandard and
  google-re2 stop being compiled. Native wheels rank above pure-Python ones, so
  protobuf keeps its upb build instead of the slow fallback.
- **jiter, tiktoken and watchfiles keep building from source.** Their maturin
  wheels carry no Mach-O install-name padding, so Homebrew relocation fails with
  "Failed changing dylib ID" (omnigent-ai#866). `pendulum` can go neither way — its wheel
  cannot be relocated and its sdist does not link on 3.14 (pyo3 leaves
  `_Py_NoneStruct` undefined) — so it takes the pure-Python wheel, which ships
  no extension module at all.
- **A dropped dependency is now an error, not a warning.** A missing sdist used
  to be skipped silently, yielding a formula whose venv lacked an import;
  `--allow-no-sdist` is the explicit waiver. The formula test also asserts
  `import re2, celpy`, since omnigent imports celpy behind `try/except
  ImportError` and would otherwise disable policies silently.
- **Delete `update-homebrew.yml`.** It raced `homebrew-tap-pr.yml` on the same
  `release: published` event and asserted on hand-maintained stanzas the
  template no longer emits, so it failed on every run. Its one worthwhile part
  moves into `homebrew-tap-pr.yml`: an admin/maintain gate on manual dispatch
  (it writes to another repo with an App token), plus
  `persist-credentials: false`. Its nightly `schedule` is deliberately NOT
  carried over -- that cron only existed because `brew
  update-python-resources` resolves through pip's `--uploaded-prior-to=P1D`
  window and so could never see a same-day release. The generator runs `uv pip
  compile --no-config` straight against PyPI, so the blindness it worked around
  no longer exists, and a nightly regeneration would just burn a runner to
  print "nothing to do".

Verified by building the generated formula in the tap, not by inspection.

- `omnigent-ai/homebrew-tap#18` contains **verbatim output of this
  `generate_formula.py`** and bottled successfully on macos-15 and macos-26
  (run 30944428771, `bottles_macos-15` / `bottles_macos-26` ≈ 37 MB each). This
  is the check that matters: it proves the generator — not a hand-edit —
  produces a buildable formula, so the next release regenerates something that
  works.
- `omnigent-ai/homebrew-tap#17` carries the same fix for the shipped 0.8.1
  formula and is green on all three runners, with `brew test` running
  `import re2, celpy`. Inspected the bottle: `celpy/__init__.py`,
  `re2/_re2.cpython-314-darwin.so`, and a relocated
  `jiter/jiter.cpython-314-darwin.so`.
- Audited every pinned wheel by replaying Homebrew's own operation,
  `install_name_tool -id <Cellar path>` against each extracted `.so`, so the
  wheel/source split is evidence-based rather than guessed.
- `python3.12 -m py_compile`, `ruff check`, `ruff format --check`, `brew style`
  (no offenses), `ruby -c`, plus stubbed-PyPI unit checks of the new failure
  paths (missing sdist is fatal, `--allow-no-sdist` waives it, abi3 accepted,
  free-threaded `cp314t` rejected).
- Confirmed generator output matches the green formula: same 100 resources,
  identical sdist/wheel split, no non-comment differences.

N/A — release tooling, no user-visible UI.

- [x] Bug fix
- [ ] Feature
- [ ] UI / frontend change
- [ ] Refactor / chore
- [ ] Docs
- [ ] Test / CI
- [ ] Breaking change

- [ ] Unit tests added / updated
- [ ] Integration tests added / updated
- [ ] E2E tests added / updated
- [x] Manual verification completed
- [ ] Existing tests cover this change
- [ ] Not applicable

The generator has no test suite in this repo, and its real contract — "the
emitted formula builds under Homebrew on macOS" — cannot be asserted here. It is
covered instead by building the generated formula on the tap's `brew test-bot`
matrix (homebrew-tap#18, bottles produced on macos-15 and macos-26). The two new
generator failure paths were exercised locally against stubbed PyPI metadata,
and every wheel pin was verified relocatable with `install_name_tool`.

`brew install omnigent` works again, and installs prebuilt wheels instead of
compiling grpcio and friends from source.

Signed-off-by: Zeyi (Rice) Fan <zeyi.f@databricks.com>
@appletechie

Copy link
Copy Markdown
Owner

Closing — artifact of the fork retirement, nothing to salvage.

This is upstream's own discord-watch-rotation-maintain.yml workflow, inherited by this fork and run here on Aug 1. The intended change is one file: +40/-56 in .github/scripts/rotation_schedule.json.

The other 38 files (~1400 lines) are the old fork's divergence leaking into the diff — deploy-image.yml, upstream-drift-sync.yml, deploy/docker/*, plus the retired harness work (antigravity_native_reader.py, grok_harness.py, acp_executor.py, kimi_native_hook.py). The bot branched rotation-schedule-20260801 from the then-diverged main; main was later reset to track upstream/main, so the diff now reads as "everything the old fork had that upstream does not" — hence CONFLICTING across 39 files.

It is also a Discord on-call rotation for upstream's maintainers, which is not this repo's concern. Upstream has since paused the cron (workflow_dispatch only), so this will not refire on its own.

@appletechie appletechie closed this Aug 7, 2026
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