Skip to content

Continual Harness ​

The harness is a durable set of operating lessons that future sessions see. UGENT can read a finished session and propose additions to it, but nothing is applied until a person approves it by hand.

Two commands: /refine proposes, /harness reviews.

Why it is propose-only ​

An agent that edits its own instructions unsupervised optimises whatever proxy it can reach. A note that quietly changes future behaviour is the wrong thing to let it write unattended, so UGENT splits the two halves: the model may propose, only a person may apply.

This is a deliberate difference from the upstream design this feature is adapted from, which applies harness edits directly.

Turning it on ​

Off by default. In ugent.toml:

toml
[harness]
enabled = true

That makes the commands available. It does not make anything happen on its own: there is no timer, no turn counter, and no hook that triggers a refinement. /refine is the only trigger. With an empty harness, an enabled harness costs one indexed database read per turn and injects nothing.

Proposing: /refine ​

Run it after a session that taught you something:

/refine

Or point it at what you care about:

/refine focus on how we handled the flaky integration tests

Two calls happen, both on the background transport so neither streams into your chat:

  1. A cheap review gate decides whether the session holds anything worth proposing. Most sessions do not, and you will see "the review gate found nothing in this session worth keeping." That is the normal answer, not a failure. Set review_gate = false to skip it and always run the full pass.
  2. If the gate says yes, a full pass reads the last 40,000 characters of the trajectory and queues proposals.

Nothing is applied. The result is always a queue you have to act on.

Reviewing: /harness ​

CommandDoes
/harness or /harness listShow pending proposals, the approved entries, and the five most recent refinement passes
/harness show <id>Full detail for one proposal, including the exact text that would be injected
/harness approve <id>Apply it to the entry set
/harness reject <id>Discard it without applying
/harness rollback <refinement-id>Undo every approved edit from one refinement pass

Proposal ids may be abbreviated. An ambiguous prefix resolves to nothing rather than guessing. A refinement id is given in full, as /harness lists it.

Approval checks the entry once more. If it changed after the edit was planned — another approval got there first, say — the approval is refused rather than overwriting that change: reject the proposal and run /refine again.

Only after approve does an entry reach the model; see How lessons reach the model.

Read show before you approve ​

/harness lists a preview; /harness show prints the entry verbatim, and that is the exact string that gets injected. Approval is the entire safety model, so it is worth the extra command.

How lessons reach the model ​

Approved entries are rendered as a [HARNESS_CONTEXT] block: shared lessons first, then personal ones (see Shared and personal lessons), each grouped as prompt notes, memories and sub-agent specs. Pending proposals never are: the model cannot see what nobody has approved.

The block tells the model what the lessons are: guidance from earlier sessions, which never overrides runtime rules, the operator's instructions, or what the user asks in the current conversation. When a session has both shared and personal lessons, it adds that the personal one wins where the two conflict, as guidance only. A lesson is a preference the model weighs, not a rule it must follow.

When a lesson arrives depends on the session:

  • A new session receives every lesson approved so far in its opening context.
  • A session already under way is told at its next user turn. A [HARNESS_CONTEXT UPDATED] block carries the new list, which replaces the earlier one, or says the lessons were withdrawn. Earlier turns are left as they were, so the conversation's prompt cache stays valid.

The block is capped at 6,000 characters. While a session has both shared and personal lessons, the shared set may take at most half and the personal set what remains, so a large shared set cannot crowd out a person's own. Entries that do not fit are left out, and the block says how many and points to /harness.

The three kinds of entry ​

KindHolds
promptA narrow behavioural addendum. Use sparingly: every prompt note is paid for on every future turn.
memoryA durable fact, decision, or recorded failure.
subagentA reusable delegation brief, including purpose, scope boundary, and acceptance criteria. Always shared: a brief names tools and roles, and one that differed per person would be configuration drift.

The upstream skill kind is deliberately absent. UGENT ships no built-in skills, and third-party skills and MCP servers are externally owned, so a skill entry would have nothing first-party to reference.

Shared and personal lessons ​

Every entry belongs to one of two sets.

SetRendered intoProposed by
SharedEvery session/refine at the TUI or REPL, or by a channel user listed in global_review_actors
PersonalOnly that person's sessions/refine by a channel user, on a session of their own

A shared proposal is decided by the operator at the TUI or REPL, or by a listed reviewer; a personal one by its owner as well. An entry keeps the set its proposal was planned in. Approval copies the proposal's owner onto the entry, and whoever approves cannot pick another set, so a proposal planned in one person's set can only ever become that person's lesson. Nothing promotes a personal lesson into the shared set.

The TUI and REPL are the operator's view. /harness there lists every person's entries and proposals and can decide or roll back any of them, so a bad lesson someone approved can still be found and undone.

Entries live in the session database ([session] database_path, by default ~/.ugent/sessions.db), not in the workspace. A lesson approved in one workspace is therefore visible in every workspace that shares the database, and a daemon with a database of its own has a set of its own.

What gets refused automatically ​

Some proposals are refused outright and never reach the queue: those that would reduce oversight or widen what may run unsupervised. Standing approval for destructive commands, skipping confirmations, disabling the firewall, hiding actions from you, or approving harness proposals automatically. A channel user's pass that would change a lesson outside their own set, or add a sub-agent spec, is refused the same way.

This exists because a harness entry is injected into future sessions — a shared one into every session, a personal one into each of its owner's — so an entry of that shape is a persistent change to how supervised the agent is, not a single bad turn. There is no legitimate version of such an edit: an operator who genuinely wants to relax a confirmation edits their own config, where the change is visible and version-controlled.

A refusal is reported separately from a malformed edit, and a pass in which every edit was refused says so rather than reporting "nothing to propose". If you hit a false positive, the log names the phrase that matched, and you can write the entry by hand.

Keeping the queue reviewable ​

max_pending (default 25) bounds the review queue in both directions: a refinement pass will not start when the queue is already full, and a single pass cannot push it past the cap. Withheld edits are counted and reported.

The cap is counted per set: the shared queue, and each person's own. One channel user filling theirs closes the queue for nobody else. It is checked again when a pass's proposals are queued, so two passes running at once cannot overfill it.

A queue nobody reviews is worse than no queue, because it buries new proposals among stale ones.

Rollback ​

/harness rollback <refinement-id> reverses every approved edit from one pass, newest first, and puts each entry back exactly as it was. Reversed proposals are recorded as rolled_back, which is not the same as rejected — the latter means a human declined it before it was ever applied.

A rollback is all or nothing:

  • If a later approval changed one of the pass's entries and is still applied, nothing is rolled back, and the message names the entry and the later proposal. Roll that one back first.
  • An entry that is no longer the one the pass left, or that now belongs to someone else, refuses the rollback the same way.
  • When a recorded previous state cannot be read, the rollback refuses rather than treating that as "there was nothing here" and deleting the entry.

Channels: off by default ​

/harness and /refine are refused over channel plugins unless you opt in:

toml
[harness]
allow_channel_review = true

With it on, each channel user works in their own personal set:

  • /refine reads only a session that user started on that channel, and its proposals are theirs.
  • /harness shows them their own pending proposals and refinement passes, and the lessons their sessions receive, shared and personal. The shared review queue stays with the operator, because a proposal quotes the session that produced it.
  • approve, reject and rollback act only on their own proposals and passes. Another person's proposal answers exactly as an unknown id does, and the refusal is logged.
  • What they approve reaches only their own sessions.

A channel that cannot say which user is asking is refused outright, rather than falling back to the shared set. Every decision records who made it: operator for the TUI or REPL, the user's actor id otherwise.

It stays off by default because a refinement pass costs model calls and builds up content. That is a decision for the operator even though it cannot affect anyone else.

Where it is enabled, approve and rollback take a two-step confirmation: the first call returns the rendered proposal and a confirmation token, and the client re-sends the command carrying it.

Deciding shared lessons from a channel ​

On a headless deployment the shared set lives in the daemon's session database, and a TUI on another machine runs against a database of its own, so a channel is usually the only way to reach it. List the people who may decide for the shared set from a channel:

toml
[harness]
allow_channel_review = true
global_review_actors = ["federation:<issuer>:<subject>"]

A listed person has the operator's reach: they see every person's proposals, decide any of them, and their own /refine passes propose shared lessons. Their decisions are recorded under their own id.

Only list someone whose identity the channel authenticates on the server, as the web workspace does. A channel that forwards whatever user id its client sends would let anyone who can type that id decide what every session sees.

Configuration reference ​

toml
[harness]
# Make the commands available and inject approved entries. Off by default.
enabled = false

# Cheap review call before each pass. On by default: most sessions yield
# nothing, and the gate costs far less than the pass it avoids.
review_gate = true

# Pending proposals allowed, counted per set. Bounds both entry to a pass and
# how many one pass may add.
max_pending = 25

# Whether /harness and /refine are reachable over channel plugins. A channel
# user works in their own personal set.
allow_channel_review = false

# Channel users who decide for the shared set, as the operator does. Only list
# people the channel authenticates server-side.
global_review_actors = []

# Optional dedicated model for refinement. Unset means the session model.
[harness.refine]
provider = "openai"
model = "gpt-5-mini"

What this is not ​

  • Not memory. Memory is a recall engine over a large corpus, searched per turn. The harness is a small enumerable set injected wholesale. Only the injection seam is shared.
  • Not per-workspace. Lessons live in the session database, not the workspace, so a lesson approved in one workspace reaches every other workspace that uses the same database. Worth knowing before you approve something project-specific; see Shared and personal lessons.
  • Not automatic. Nothing refines on a schedule or at a compaction boundary.

Released under the Private Beta License.