Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions docs/ARCHITECTURE.md

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ $ corbits exec "Add JWT auth to the API"
$ corbits run "Add JWT auth to the API"
```

Same directors, tools, permissions, MCP, plugins, and hooks as the TUI — without the OpenTUI shell. The exec bootstrap is a deliberate fork of the TUI path (not a shared factory yet); see `docs/ARCHITECTURE.md` “Exec Runner” for intentional deltas (no workflow controller or goal governor; single primary send; non-interactive permission gate). Compaction continuation matches TUI so long runs do not stall after compact. Streams assistant text to stdout for scripts and CI. Non-interactive by default: actions that need operator approval are denied unless `--dangerously-skip-permissions` is set (or auto mode covers them). `ask_operator` reads a single line from stdin when available.
Same directors, tools, permissions, MCP, plugins, and hooks as the TUI — without the OpenTUI shell. The exec bootstrap is a deliberate fork of the TUI path (not a shared factory yet); see `docs/ARCHITECTURE.md` “Exec Runner” for intentional deltas (no workflow controller; single primary send; non-interactive permission gate). Compaction continuation matches TUI so long runs do not stall after compact. Streams assistant text to stdout for scripts and CI. Non-interactive by default: actions that need operator approval are denied unless `--dangerously-skip-permissions` is set (or auto mode covers them). `ask_operator` reads a single line from stdin when available.

Local multi-model capability checks use this path (`bun run eval:capability`); see `evals/capability/README.md`.

Expand Down Expand Up @@ -76,14 +76,12 @@ Continues from the last saved state in the working directory.

## Slash Commands (TUI)

The TUI has an extensible slash-command framework. Built-ins include `/help` (shortcut + command overlay), `/model` (open the agent configuration surface — connect providers with **c** / **Ctrl+A**, pick models, tiers, and profiles), `/settings`, `/permissions`, `/plugins`, `/clear`, `/new`, `/mcp`, and `/goal` (session goal: expand a brief into an acceptance checklist and auto-continue until every criterion is done — see `/goal [turns] <brief>`, `/goal pause|resume|clear|status`, optional `--tokens N` / `--replace`), plus a `/<name>` command per available workflow. Plugins can register additional commands.
The TUI has an extensible slash-command framework. Built-ins include `/help` (shortcut + command overlay), `/model` (open the agent configuration surface — connect providers with **c** / **Ctrl+A**, pick models, tiers, and profiles), `/settings`, `/permissions`, `/plugins`, `/clear`, `/new`, and `/mcp`, plus a `/<name>` command per available workflow. Plugins can register additional commands.

Providers are **models-first**: there is no standalone `/login` command. `/model` opens on a **model list** (Recent, Favorites, then providers) so you pick a model without drilling provider first. **Alt+A** (or **c**) opens Connect; **Alt+F** toggles favorite on the highlighted model; **a** opens the advanced provider drill-down (edit/delete/tiers). Connect lists first-class providers (OpenAI dual-path ChatGPT OAuth or API key, xAI, OpenCode Zen, Anthropic, Google, OpenCode Go, Z.AI Coding Plan, Custom). OAuth providers open their existing browser login; API-key providers show an **auth-only** form (key + fixed catalog base URL), validate, and persist pre-seeded models for immediate selection. OpenCode Go routes each model by its protocol metadata (chat completions, OpenAI responses, or Anthropic messages) and can show subscription usage in the status bar when active (rolling 5h / weekly / monthly windows when the usage API responds; omitted on auth or network failure). When Go returns a quota or rate-limit error — including some HTTP 400 responses that carry limit payloads — Corbits classifies them so quota aborts cleanly and short provider rate limits remain retryable. On a free-tier or subscription quota hit, wait for the window to reset or use OpenCode Zen free models.

`/model` opens a dedicated full-screen modal — the single place agent configuration lives. The default view is models-first (Recent / Favorites / Providers); connect, tiers, and profiles remain reachable from the same surface. A switch applies to the running session immediately (no restart), and can be saved as this project's default (written to the per-repo selection file). Recent and favorite model pairs are stored in global settings (no credentials).

`/goal <brief>` arms a session-scoped goal governor. The operator brief is **not** the completion condition: the agent must clarify success (via `ask_operator` when vague) and expand it into a multi-item **acceptance** checklist with `manage_goal` *before* substantial work. Work steps go in `manage_tasks` (shown as **Work** while a goal is active) — separate from acceptance. Lifecycle phases surface in the UI: **planning** (define Acceptance) → **implementing** (Work primary; Acceptance compact; `doing` on a criterion stays here) → **reviewing** (starts when any criterion is `done` or `blocked`) → **completed** (all non-cancelled criteria done; auto-achieves). After each clean yield the agent is re-inferred until every acceptance criterion is done, a finite turn/token budget soft-stops, or the operator pauses/clears. **Default turn budget is unlimited** (`0`); an optional leading integer caps continues (`/goal 40 ship the feature`). Resume restores a prior goal as **paused** (never silently re-armed); unlimited goals stay unlimited on resume, finite ones get headroom. While a goal is **active**, permission prompts that still need a human answer auto-skip after ~15s with a note back to the agent (human may be away — continue another way); the operator can still approve/deny earlier. Pair with auto mode and/or `--dangerously-skip-permissions` for longer unattended runs. Goal mode does not shrink tools, skills, slash commands, sub-agents, or MCP.

## Lifecycle Hooks

Config-driven `postTurn` and `postRun` hooks (TypeScript or shell) run automatically, discovered from `.corbits/hooks` (per-repo) and `~/.corbits/hooks` (global). `postTurn` receives aggregated turn context (tool calls, results, token usage, duration); `postRun` receives a run summary. The TUI hook panel lists discovered hooks and lets the user enable/disable them. See `docs/HOOKS.md`.
Expand Down
6 changes: 3 additions & 3 deletions docs/TUI.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ palette," a plain question — never the word "overlay."

There is no titlebar, no status strip, and no key-hint row as permanent
chrome. The prompt box is the only permanent chrome in the shell: it is
anchored at the bottom in every state, and everything else — goal/task/agents
anchored at the bottom in every state, and everything else — task/agents
strips, notices, banners, the overlay host — is optional and collapses to
zero rows when it has nothing to say (`src/tui-opentui/geometry/zones.ts`).
The transcript is residual: whatever rows remain after chrome and any open
Expand All @@ -32,7 +32,7 @@ terminal with nothing optional showing, the transcript floor is 12 rows
proposed 8 rows (`OVERLAY_TRANSCRIPT_FLOOR`) so the log stays glanceable
underneath a permission prompt. When space is scarce, collapse follows a
fixed order — transient banners first, then settings/plugin notices, then
goal/task/agents strips, then progress, then the prompt itself shrinks one
task/agents strips, then progress, then the prompt itself shrinks one
row at a time down to its 3-row base — never the transcript
(`COLLAPSE_ORDER` in `zones.ts`).

Expand Down Expand Up @@ -134,7 +134,7 @@ agent, with `agentId` as a tiebreak for a simultaneous fan-out.

Under space pressure, the zone shrinks one row at a time toward 1 rather
than collapsing straight to 0 (`COLLAPSE_ORDER` treats it like `progress`,
not like the single-row `goal`/`task` strips) — a 1-row panel still carries
not like the single-row `task` strip) — a 1-row panel still carries
the stalest agent plus its `+N more` trailer, so it stays meaningful all
the way down. Only once every other collapsible zone ahead of it in
`COLLAPSE_ORDER` and the panel itself are exhausted does it reach 0, the
Expand Down
40 changes: 3 additions & 37 deletions src/agent/director.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import { type } from "arktype";
import { applyManageTasks, hasActiveTasks, parseManageTasksArgs, type Task } from "./tasks.js";
import { createCorbitsRetryPolicy } from "./retry-policy.js";
import { isInternalRecoveryAbortRaw } from "../inference-abort.js";
import type { GoalGovernor } from "./goal.js";
import { evidenceFromTurns } from "./goal-evaluator.js";
import { LOG_NAMESPACE_ROOT } from "../branding.js";
import { resolveModelFamilyPolicy, type ModelFamilyPolicy } from "./model-family-policy.js";
import { PRESENT_VIEW_PRIMITIVES_GUIDANCE } from "./tool-schema-normalize.js";
Expand Down Expand Up @@ -67,8 +65,8 @@ function inferWithNudge(
// Assumes a bare wait always means the turn is over. That holds for every
// current wait path: DefaultDirector in conversational mode (the only mode
// ChatDirector uses) yields a bare wait only on an empty model turn, and its
// halt path already carries a reply; the compaction, workflow, open-task, and
// goal rewrites either keep those terminals or replace them with an infer.
// halt path already carries a reply; the compaction, workflow, and open-task
// rewrites either keep those terminals or replace them with an infer.
// A future wait that pauses mid-turn while expecting more work must not be
// settled here.
function ensureCycleSettlesWithReply(
Expand Down Expand Up @@ -363,7 +361,6 @@ class ChatDirectorImpl extends DefaultDirector {
private lastTaskSummary: string | undefined;
private startedAt = Date.now();
private readonly compaction: CompactionGovernor;
private goal: GoalGovernor | undefined;
private readonly modelFamilyPolicy: ModelFamilyPolicy;
// Consecutive assistant turns that contain tool calls and no text. Reset on
// any turn with text and on every fresh user message — a weak model that
Expand Down Expand Up @@ -393,14 +390,6 @@ class ChatDirectorImpl extends DefaultDirector {
this.workflowCoordinator = coordinator;
}

setGoalGovernor(goal: GoalGovernor | undefined): void {
this.goal = goal;
}

getGoalGovernor(): GoalGovernor | undefined {
return this.goal;
}

updateToolDefinitions(toolDefinitions: ToolDefinition[]): void {
this._toolDefinitions = toolDefinitions;
}
Expand Down Expand Up @@ -629,16 +618,6 @@ class ChatDirectorImpl extends DefaultDirector {
this.pendingToolOnlyNudge = true;
}

// Attribute main-loop tokens to an active goal for soft token budgets.
if (this.goal !== undefined) {
const u = event.usage;
if (u !== undefined) {
const n =
(typeof u.input === "number" ? u.input : 0) +
(typeof u.output === "number" ? u.output : 0);
if (n > 0) this.goal.noteMainTokens(n);
}
}
if (this.workflowCoordinator?.isActive()) {
if (hasToolCalls) {
this.workflowIdleTurns = 0;
Expand Down Expand Up @@ -723,7 +702,7 @@ class ChatDirectorImpl extends DefaultDirector {
const compacted = this.compaction.interceptActions(event, baseActions, capabilities);
if (compacted !== null) return compacted;

// Loop protection takes precedence over workflow/open-task/goal
// Loop protection takes precedence over workflow/open-task
// continuation nudges below: those exist to keep a session moving,
// which is exactly the behavior the pause is guarding against. A tool
// call turn (like the one that triggered this) must still execute
Expand Down Expand Up @@ -789,17 +768,6 @@ class ChatDirectorImpl extends DefaultDirector {
}
}

// Goal continue-rule runs last among terminal rewrites so open-task and
// workflow nudges keep precedence. Only fires when we would otherwise yield.
if (this.goal !== undefined) {
const goalRewrite = await this.goal.interceptTerminal(baseActions, capabilities, {
atWorkflowGate,
lastTurnHadContent: this.lastInferenceTurnHadContent,
evidence: evidenceFromTurns(state.turns ?? []),
});
if (goalRewrite !== null) return goalRewrite;
}

return base;
}
}
Expand Down Expand Up @@ -839,8 +807,6 @@ export function hydrateTasksFromTurns(turns: ConversationTurn[]): Task[] {
export interface ChatDirector extends ReactorDirector {
updateToolDefinitions(toolDefinitions: ToolDefinition[]): void;
setWorkflowCoordinator(coordinator: WorkflowCoordinator | undefined): void;
setGoalGovernor(goal: GoalGovernor | undefined): void;
getGoalGovernor(): GoalGovernor | undefined;
getTasks(): Task[];
restoreTasks(tasks: Task[]): void;
getContextEstimate(): { tokens: number; isEstimate: boolean };
Expand Down
124 changes: 0 additions & 124 deletions src/agent/goal-evaluator.test.ts

This file was deleted.

Loading
Loading