Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ npx skills add iii-hq/iii --all
|---|---|---|
| [`acp`](acp/) | Rust | Agent Client Protocol surface — stdio JSON-RPC, exposes iii agents as ACP sessions. |
| [`approval-gate`](approval-gate/) | Rust | Human-in-the-loop approval gate — evaluates each function call (continue / deny / hold), holds pending calls for a human, and emits `approval::pending-*` events. Binds the harness `pre_dispatch` hook. See [`approval-gate/architecture/`](approval-gate/architecture/). |
| [`harness`](harness/) | Node | TS port of the iii harness stack — bundles `harness` (provider registry + credentials/settings/permissions via the `configuration` worker), `turn-orchestrator`, `approval-gate`, `hook-fanout`, `models-catalog`, the `provider-*` workers, `llm-budget`, and `context-compaction` as one pnpm monorepo. Conversations persist in `session-manager`. See [`harness/README.md`](harness/README.md). |
| [`harness`](harness/) | Node | TS port of the iii harness stack — bundles `harness` (provider registry + credentials/settings/permissions via the `configuration` worker), `turn-orchestrator`, `hook-fanout`, `models-catalog`, the `provider-*` workers, `llm-budget`, and `context-compaction` as one pnpm monorepo. Approval is delegated to the standalone `approval-gate` worker via the `pre_dispatch` hook. Conversations persist in `session-manager`. See [`harness/README.md`](harness/README.md). |
| [`claude-code`](claude-code/) | Node | Claude Code as an iii worker — `claude::*` runs headless Claude Code turns, mirrors raw messages onto `claude::events`, and streams AgentEvent frames onto `agent::events`. |
| [`session-manager`](session-manager/) | Rust | Durable, reactive, branching conversation store — fourteen `session::*` functions plus six trigger types; the transcript backend for `harness` and `console`. See [`session-manager/architecture/`](session-manager/architecture/). |
| [`database`](database/) | Rust | PostgreSQL, MySQL, and SQLite client — query, execute, transactions, prepared statements, and change feeds. |
Expand Down
29 changes: 18 additions & 11 deletions console/web/src/lib/backend/approval-settings.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
* RPC adapters for the per-session approval settings (`approval::*`
* handlers in harness/src/approval-gate/settings/). These are
* functions on the standalone approval-gate worker). These are
* user-initiated RPCs only — agent function calls cannot reach them
* (the turn-orchestrator hook hard-denies these function ids).
* (the gate hard-denies approval::* function ids).
*/

import { getIiiClient } from '@/lib/iii-client'
Expand All @@ -12,7 +12,7 @@ export type PermissionMode = 'manual' | 'auto' | 'full'
export interface AlwaysAllowEntry {
function_id: string
granted_at: number
granted_by: 'user_click'
granted_by: 'user_click' | 'seed'
}

export interface ApprovalSettings {
Expand Down Expand Up @@ -40,15 +40,22 @@ function coerceEntries(raw: unknown): AlwaysAllowEntry[] {
(entry): AlwaysAllowEntry => ({
function_id: String(entry.function_id ?? ''),
granted_at: Number(entry.granted_at ?? 0),
granted_by: 'user_click',
granted_by: entry.granted_by === 'seed' ? 'seed' : 'user_click',
}),
)
.filter((entry) => entry.function_id.length > 0)
}

function coerceSettings(raw: unknown): ApprovalSettings {
if (!raw || typeof raw !== 'object') return DEFAULT_APPROVAL_SETTINGS
const r = raw as Record<string, unknown>
// The approval-gate worker wraps the record: mutations return
// { settings }, get_settings returns { settings, source }.
const outer = raw as Record<string, unknown>
const r = (
outer.settings && typeof outer.settings === 'object'
? outer.settings
: outer
) as Record<string, unknown>
const mode: PermissionMode =
r.mode === 'auto' || r.mode === 'full' ? r.mode : 'manual'
return {
Expand All @@ -63,7 +70,7 @@ export async function getApprovalSettings(
sessionId: string,
): Promise<ApprovalSettings> {
const client = await getIiiClient()
const raw = await client.call('approval::get_settings', {
const raw = await client.call('approval::get-settings', {
session_id: sessionId,
})
return coerceSettings(raw)
Expand All @@ -74,7 +81,7 @@ export async function setApprovalMode(
mode: PermissionMode,
): Promise<ApprovalSettings> {
const client = await getIiiClient()
const raw = await client.call('approval::set_mode', {
const raw = await client.call('approval::set-mode', {
session_id: sessionId,
mode,
})
Expand All @@ -86,7 +93,7 @@ export async function addAlwaysAllow(
functionId: string,
): Promise<ApprovalSettings> {
const client = await getIiiClient()
const raw = await client.call('approval::add_always_allow', {
const raw = await client.call('approval::add-always-allow', {
session_id: sessionId,
function_id: functionId,
})
Expand All @@ -98,7 +105,7 @@ export async function removeAlwaysAllow(
functionId: string,
): Promise<ApprovalSettings> {
const client = await getIiiClient()
const raw = await client.call('approval::remove_always_allow', {
const raw = await client.call('approval::remove-always-allow', {
session_id: sessionId,
function_id: functionId,
})
Expand All @@ -110,7 +117,7 @@ export async function approveAlways(
functionId: string,
): Promise<ApprovalSettings> {
const client = await getIiiClient()
const raw = await client.call('approval::approve_always', {
const raw = await client.call('approval::approve-always', {
session_id: sessionId,
function_id: functionId,
})
Expand All @@ -120,7 +127,7 @@ export async function approveAlways(
export async function clearApprovalSettings(sessionId: string): Promise<void> {
const client = await getIiiClient()
await client
.call('approval::clear_settings', { session_id: sessionId })
.call('approval::clear-settings', { session_id: sessionId })
.catch(() => {
/* best-effort cleanup on conversation deletion */
})
Expand Down
10 changes: 5 additions & 5 deletions harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ Read [The Harness Is the Backend](https://www.linkedin.com/pulse/harness-backend

**New capability, new worker.** When the harness needs something else (shell, database, coder, another provider), you add a worker, not a fork of the orchestrator. Published workers install from the [iii worker registry](https://workers.iii.dev) with `iii worker add <name>`; they register on the iii engine and show up in the live catalog.

**Turns, approvals, budgets.** Seven-state durable turn FSM with queue-backed steps. Approval gate with YAML permissions, parallel tool batches, pending state across reload, fail-closed when policy is unreachable. Workspace and agent budget caps. Five provider workers behind one registry.
**Turns, approvals, budgets.** Seven-state durable turn FSM with queue-backed steps. A `harness::hook::pre-dispatch` trigger type gates every agent function call — the standalone [approval-gate worker](../approval-gate/) binds its policy there (permission modes, YAML rules, pending inbox) and settles holds via `harness::function::resolve`; the chain fails closed when a bound hook is unreachable. Parallel tool batches, pending state across reload. Workspace and agent budget caps. Five provider workers behind one registry.

**Context compaction.** Long sessions exceed model windows. The `context-compaction` worker compacts history as turns accumulate and backs the console `/compact` command.

---

## What ships here

Fourteen workers in one TypeScript package, one folder per worker, one feature per file:
One folder per worker, one feature per file:

| Concern | Workers |
| --- | --- |
| Orchestration | `turn-orchestrator` (durable turn FSM), `hook-fanout` |
| Governance | `harness` (permissions, provider registry, UI fanout), `approval-gate` |
| Orchestration | `turn-orchestrator` (durable turn FSM, pre_dispatch hook point, `harness::function::resolve`), `hook-fanout` |
| Governance | `harness` (yaml policy rules, UI fanout) |
| Context | `context-compaction` (keeps long sessions inside the model window) |
| Models | `models-catalog`, `provider-anthropic`, `provider-openai`, `provider-kimi`, `provider-lmstudio`, `provider-llamacpp` |
| Cost | `llm-budget` |

Rust workers (`shell`, `iii-directory`, `session-manager` — the durable, reactive conversation store the harness drives through `session::*`) and engine builtins (`state::*`, `stream::*`, `iii::durable::*`) stay on the same bus; this package does not reimplement them.
Rust workers (`shell`, `iii-directory`, `session-manager` — the durable, reactive conversation store the harness drives through `session::*`, and `approval-gate` — the standalone approval policy worker at the repo root) and engine builtins (`state::*`, `stream::*`, `iii::durable::*`) stay on the same bus; this package does not reimplement them.

---

Expand Down
Loading
Loading