Skip to content

Feat/agon everywhere#232

Merged
cukas merged 7 commits into
mainfrom
feat/agon-everywhere
Jun 24, 2026
Merged

Feat/agon everywhere#232
cukas merged 7 commits into
mainfrom
feat/agon-everywhere

Conversation

@cukas

@cukas cukas commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

No description provided.

The v1 HeadlessTurnBrainClient does one engine dispatch per turn and declares
clientCapabilities:'unsupported'. This adds the v2 brain the BrainClient contract
always anticipated: a bounded ReAct loop that pulls client-lent tools mid-turn.

- registerCapability/unregisterCapability store tools a client (the browser panel)
  lends the brain; runTurn runs the loop: dispatch engine → parse a __AGON_TOOL__
  {name,input} sentinel → yield capability-request → await provideCapabilityResult
  → feed the result back into the transcript → repeat → final answer.
- Destructive tools (CapabilitySpec.isDestructive) first yield approval-request and
  await provideApproval; approve-session/deny-session are remembered for the turn-
  brain so the same tool isn't gated twice. 'abort' ends the turn.
- Engine-agnostic: parseAgentToolCall is forgiving (tolerates prose/fences, a
  garbled/absent sentinel reads as a final answer). Bounded by MAX_AGENT_STEPS;
  every capability/approval await is abort-aware and times out (no hung brain).
- An agent screenshot (a 'data:' result) is decoded and shown to the engine as
  vision on the next dispatch, not dumped as text.
- Declares clientCapabilities:'supported', approvalArbitration:'host-only'.

24 unit tests: the loop (read tool, approval gate, deny/abort, approve-session,
unknown tool, step limit), the control surface (acks, detach cleanup, mid-turn
cancel), and the pure helpers (forgiving sentinel parse, prompt/transcript build).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…ts + bind the agentic brain

agon serve now binds AgenticTurnBrainClient, and AgonServe routes the agent
control plane so the browser panel can lend tools and answer the brain mid-turn.

- AgonServe: POST /register-capability, /unregister-capability, /capability-result,
  /approval. These call the brain DIRECTLY (never chained on turnTail), so a
  capability-request the live turn is awaiting can be answered while handleSend still
  holds the per-session write lock — the no-deadlock property the design depends on.
- The turn drain flushes the ledger immediately after a capability-request/
  approval-request so it reaches the panel without the ~50ms coalesce wait.
- Client-supplied capability specs are validated (string name+description) before
  reaching the brain; the approval decision enum is validated too.
- serve.kern binds the agentic brain (degrades to single-dispatch with no tools) and
  trims the base system prompt — the agent role + tool protocol now come from
  buildAgentSystemPrompt; the old "you cannot click or type" guidance is gone.

serve-command tests: control-endpoint validation (a valid register returns 'accepted',
proving the agentic brain is bound, not the 'unsupported' v1), and an end-to-end
wire round-trip proving /send blocks on a capability-request (over SSE) until
/capability-result lands — no deadlock.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…lient ownership

6-engine agon review of the agent brain + wire. Real findings fixed; false
positives dismissed with reasoning.

- FAIL-SAFE approval gate (claude, important): the gate now keys on !isReadOnly,
  not the client's isDestructive flag. A tool must be EXPLICITLY read-only to skip
  approval — a mis-/under-declared mutating tool is gated, never silently run. The
  engine-facing label is aligned to isReadOnly so a mutating tool is never shown as
  "(read-only)". Since the engine picks tools partly from untrusted page text, this
  closes a prompt-injection bypass of the "ask before acting" gate.
- Per-client OWNERSHIP enforcement (codex 0.97/0.98, kimi 0.85): provideCapabilityResult
  and provideApproval now accept a reply ONLY from the client the request was routed
  to (capability owner / turn submitter) — enforcing the declared host-only arbitration
  instead of merely advertising it. A second token-holding client can no longer spoof
  another's tool result or approve its destructive action; a mismatch is rejected
  WITHOUT consuming the pending entry. unregisterCapability is ownership-checked too.
- Spec size cap (minimax): a registered capability spec is bounded at 4 KiB so it
  can't bloat every subsequent dispatch's system prompt; inputSchema render is sliced.

Dismissed (verified): close() timer "leak" (close aborts controllers first, firing
onAbort→clearTimeout), description-mandatory mismatch (it's required by the type),
timeout-ends-turn (deliberate fail-fast: a client timeout means the browser is gone,
not engine-recoverable like deny/unknown).

Two new tests: the fail-safe gate (a tool with neither flag is still gated) and
ownership (a non-owner client's result is rejected; the owner's is accepted).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…ool call

Field report: glm-5.2 replied "Let me navigate to your LinkedIn profile…" with NO
__AGON_TOOL__ line, so the loop treated the prose as a final answer and ended — the
agent SAID it would act and then did nothing. This is the weak-engine tool-call
reliability risk; two mitigations:

- Far more forceful agent protocol prompt: explicit "do NOT narrate then stop —
  narration does nothing, only a tool line acts", with concrete __AGON_TOOL__ examples.
- Narration recovery in runTurn: when a reply has no tool call but looksLikeActionIntent
  (a short "Let me…/I'll…" + an action verb, head-only match so a real prose answer
  isn't caught), nudge the engine to actually emit the tool line instead of ending.
  Bounded by MAX_NARRATION_RETRIES (2) so a chatty engine can't loop; after the budget
  it surfaces the prose as the answer.

3 new tests: looksLikeActionIntent (preamble vs real answer), the nudge→act path, and
the give-up-after-budget path (no infinite loop). Engines that DO emit tool calls
(claude/codex) are unaffected — the nudge only fires on no-tool + action-intent.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
The browser engine selector was fed registry.listIds() — every engine
definition on disk, including ones with no API key/binary and ones the
user explicitly hid or removed. Switch to registry.activeIds(config):
available (an API key env var is set OR the CLI binary is on PATH) AND
not in hiddenEngines/removedEngines, honoring engineActivationMode.

Canonicalize the bound default via resolveId so an alias-started serve
(--engine kimi) never double-lists against the canonical id
(kimi-for-coding-*), and force-include it on a FRESH array (no in-place
mutation of the method's return) so it always shows as the current
selection even if its availability check is borderline.

6-engine agon review closed all importants: availableIds ignored the
activation/visibility rules; the unshift mutated the method's return;
an alias could double-list the same engine.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
…`agon serve`

A second client of a running `agon serve`: a turn typed in the TERMINAL is answered
by serve's agentic brain using your browser. Page-tool calls
(navigate/readPage/screenshot/click/type) route to the side panel that registered
them — independent of who submitted the turn — so they run in your Chrome. Approvals
route to the submitter (us): a page-changing action prompts in the terminal
(y/a/n/s), or --auto-approve; read-only tools need no approval.

A blocking POST /send drives the turn while an SSE /events reader renders live
events and answers our approval-requests. The connection is auto-discovered from the
0600 serve files (or --url/--token). Requires the side panel open + attached — that
is where the page tools live; with none registered the brain just answers in text.

BrainEvent 'approval-request' gains an optional targetClientId (mirrors
capability-request), stamped to the turn submitter, so the prompt routes to ONE
surface — the browser panel skips a terminal-driven turn's approval.

Hardened: SSE failure surfaced (the turn no longer runs blind), /approval res.ok
checked, non-TTY stdin denied rather than hung, the post-send drain waits for the
answer (bounded), frame parsing stays O(n), and the real session id comes from
/attach. 26 unit tests for the pure helpers (discovery, SSE framing, rendering,
approval routing).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
… Cesar

`/chrome <task>` in the REPL drives a browser-agent turn and renders it inline. The
bridge is resolved automatically: reuse a live `agon serve` if one is up, else EMBED
one in-process (the same agentic brain) on an ephemeral loopback port and write the
0600 connection file the extension's side panel auto-connects through — so there's no
separate `agon serve` terminal. The embedded brain is the agentic ReAct brain, never
Cesar, so a browser turn can't write the live session (the split-brain reservation);
its RESULT is fed back to Cesar instead (the /research, /council continuation path).

Page-changing actions are approved through the REPL's own permission-ask Y/N UI (the
mechanism Cesar already uses), not a raw prompt; read-only tools (read/screenshot) run
without asking. Reuses agon drive's two-connection client (blocking /send + SSE tail)
with REPL-native render + approval.

Wired across the REPL surfaces (SLASH_COMMANDS, parser, dispatch case, handler export).
The embedded bridge is a re-entrancy-safe per-REPL singleton with synchronous exit
cleanup; the origin allowlist is restricted to chrome-/moz-extension://, the live-bridge
probe is bounded + identity-checked (sessionId match), every bridge fetch is abort-linked,
the approval prompt is raced against abort (no Ctrl-C hang), and a no-result turn never
feeds Cesar stale context. 6-engine agon review: all blocking + high-confidence findings
closed.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
@cukas cukas merged commit d32f22a into main Jun 24, 2026
2 checks passed
@cukas cukas deleted the feat/agon-everywhere branch June 24, 2026 09:17
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.

2 participants