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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,10 @@ framework.
below 1, which its `reset.discardedMultiplier` shows. With the host
stopped, reset the first kind's multiplier once, and re-stamp the second
kind's checked value, as the **Operators** entry of context-manager
0.14.0's changelog describes.
0.14.0's changelog describes. The reset is for a store that already
opened on 0.14.0 and ran a committing compile, which re-stamped its
multiplier: `^0.14.0` now installs 0.14.1, which keeps a still-unstamped
multiplier provisional and replaces or drops it on its own.
- **What else hosts meet with context-manager 0.14.0** (added after the
release): a preview, or any compile made with `dryRun`, leaves the context
manager's estimate, its calibration and a pending kv-unified draft's marker
Expand Down
2 changes: 1 addition & 1 deletion changelog.d/body-removals.fixed.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- Removing a single message that belongs to a body, a one-shard body's included, no longer fails: context-manager's `removeMessage` refuses any message in a body, so agent-framework now removes one as a range in offline recovery's suppressions, and in a Discord suppression resumed after a crash. The host `hide` command now hides a message stored as a body (a long one, in shards) whole, with every stored copy of it (a write cut short leaves a partial copy before its retry), and its operator-log entry lists at most 50 of the ids it removed, with `removedIdsTruncated` when there were more. It's refused as busy while a surgery holds the store, and a message with no Discord id never matches one. A module's removal of a message it added, and the coalescer's, take its whole body too, and an automatic rewind whose newest message is a shard of a body takes the whole body.
- Removing a single message that belongs to a body, a one-shard body's included, no longer fails: context-manager's `removeMessage` refuses any message in a body, so agent-framework now removes one as a range in offline recovery's suppressions, and in a Discord suppression resumed after a crash. The host `hide` command now hides a message stored as a body (a long one, in shards) whole, with every stored copy of it (a write cut short leaves a partial copy before its retry). Its range form takes what was stored between its two messages, from the earlier one's first copy through the later one's first, and every copy of both, each whole, so a copy stored outside that span is taken on its own, and nothing else stored after the later message began is taken. Offline recovery's suppressed ranges (`suppressRanges`) take the same: before, a range ran through the later message's last copy, taking what was stored between that message's copies, and left out a copy of the earlier message stored after it. Either form's operator-log entry lists at most 50 of the ids it removed, with `removedIdsTruncated` when there were more. It's refused as busy while a surgery holds the store, and a message with no Discord id never matches one. A module's removal of a message it added, and the coalescer's, take its whole body too, and an automatic rewind whose newest message is a shard of a body takes the whole body.
4 changes: 2 additions & 2 deletions changelog.d/cm-0.14.changed.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
- A context-maintenance pass no longer fails as a whole when one agent's readiness can't be read. context-manager 0.14, which agent-framework has required since 0.21.1, refuses its readiness queries while a strategy's initialization is pending (inside `switchBranch`, `fork`, `setStrategy` or `ContextManager.open`) or has failed, and a strategy can refuse on its own, as the autobiographical one does for a branch it hasn't reloaded. That agent's run now records the error, the agent gets its own `context-maintenance-failed` alert (`scope: 'agent'`), and the other agents run. Before, such a throw ended the pass for every agent, with one alert for the whole pass (`scope: 'pass'`). Anything else that throws while one agent's maintenance is prepared is that agent's failure in the same way. A failure that lasts, met by every pass, is recorded on each run, and logged and alerted once, then again when its text changes, every 15 minutes while it lasts (the alert webhook's cooldown for it, counted from the webhook's last post of it), or after that agent's maintenance has gone without a failure. `getContextMaintenanceSnapshot()` reports such an agent as `ready: false` with an `error`, and a strategy that can't give a progress snapshot reads as having none.
- While an operator surgery (`rollbackToMessage` or `suppressMessages`) or `switchBranch` holds the store, no maintenance pass starts and no over-budget drain kick is started, a running pass or drain kick starts no new tick, and the operator call waits for them to finish before it switches or forks the branch, so no maintenance work overlaps the move. Maintenance makes no provider call meanwhile: one made then, or one waiting at the agent's provider gate when the hold is taken, is withdrawn without being sent. That's no maintenance failure, and context-manager counts no attempt for it. A merge keeps its place in the queue. A chunk's compression logs context-manager's "Failed to compress chunk (hierarchical)" line with the withdrawal, and the chunk is queued again at that manager's next rebuild, such as its next message or compile. The operator call never waits on a provider cooldown, or on the retry turn a cooldown queues, which the hold keeps from starting. `switchBranch` can therefore move the store after the call returns, and the API server's `branch.switch` announces the move it made once it has, while `branch.current`, the branch list, `/health` and `/status` read the store's branch whenever they report one. `maintenanceTick()` answers `ran: false` meanwhile, and `resume()` refuses with `OperatorActionError` (`agent-busy`), as the other operator calls do, including when the surgery took the store while `resume()` waited. Resume once the surgery has finished.
- A context-maintenance pass no longer fails as a whole when one agent's readiness can't be read. context-manager 0.14, which agent-framework has required since 0.21.1, refuses its readiness queries while a strategy's initialization is pending (inside `switchBranch`, `fork`, `setStrategy` or `ContextManager.open`) or has failed, and a strategy can refuse on its own, as the autobiographical one does for a branch it hasn't reloaded. That agent's run now records the error, the agent gets its own `context-maintenance-failed` alert (`scope: 'agent'`), and the other agents run. Before, such a throw ended the pass for every agent, with one alert for the whole pass (`scope: 'pass'`). Anything else that throws while one agent's maintenance is prepared is that agent's failure in the same way. A failure that lasts, met by every pass, is logged, alerted and recorded in the maintenance history when it's first met, then again when its text changes, every 15 minutes while it lasts (the alert webhook's cooldown for it, counted from the webhook's last post of it), or after that agent's maintenance has gone without a failure. A pass that met only failures already reported, and took no tick, isn't added to the history unless an operator's `maintain` (`maintenanceTick()`) asked for it, so a lasting failure no longer pushes out the runs that did work. `getContextMaintenanceSnapshot()` reports such an agent as `ready: false` with an `error`, and a strategy that can't give a progress snapshot reads as having none.
- While an operator surgery (`rollbackToMessage` or `suppressMessages`) or `switchBranch` holds the store, no maintenance pass starts and no over-budget drain kick is started, a running pass or drain kick starts no new tick, and the operator call waits for them to finish before it switches or forks the branch, so no maintenance work overlaps the move. `stop()` waits for them the same way, a drain kick included, before it closes the store, so the tick in flight ends before the store closes, and once `stop()` has begun, no drain kick starts and none starts a new tick. Maintenance makes no provider call meanwhile, a conversation agent's included: one made under the hold, or once `stop()` has begun, is withdrawn without being sent, and so is a resident's call waiting at its provider gate when the hold is taken or `stop()` begins. That's no maintenance failure, and for a call outside context-manager 0.14's compression fallback ladder, a merge's or a chunk's canonical call with its retries after a `tool_use` stop, context-manager counts no attempt. One inside the ladder, after a chunk's canonical call was refused or cut short, is recorded there as a provider error, and once every rung has failed, the chunk's request family is quarantined, with its alert. Before, a resident's call made once `stop()` had begun was refused as a failure, so a restart that caught a maintenance tick before one of its calls logged a maintenance failure and raised a `context-maintenance-failed` alert; now the agent's maintenance logs that it stopped. An over-budget compile's `context-refusal` alert says whether the drain breaker kicked: `kicking`, `already draining`, or `not kicked` with the reason, where it said `kicking` whenever the agent was found, and `unavailable (no agent handle)` for one it can't find, as before. A kick joins what `stop()`, an operator surgery and `switchBranch` wait for before the alert is raised, so a listener on the alert that begins any of them waits for the kick, which then starts no tick. A merge keeps its place in the queue. When a chunk's compression is withdrawn outside the ladder, context-manager logs its "Failed to compress chunk (hierarchical)" line with the withdrawal, and the chunk is queued again at that manager's next rebuild, such as its next message or sent compile. The operator call never waits on a provider cooldown, or on the retry turn a cooldown queues, which the hold keeps from starting. `switchBranch` can therefore move the store after the call returns, and the API server's `branch.switch` announces the move it made once it has, while `branch.current`, the branch list, `/health` and `/status` read the store's branch whenever they report one. `maintenanceTick()` answers `ran: false` meanwhile, and `resume()` refuses with `OperatorActionError` (`agent-busy`), as the other operator calls do, including when the surgery took the store while `resume()` waited. Resume once the surgery has finished.
2 changes: 1 addition & 1 deletion changelog.d/cm-0.14.fixed.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
- `resume()` no longer throws when one agent's runtime settings can't be read, as when its context manager (0.14, required since 0.21.1) refuses after its strategy failed to initialize. It warns, naming the agent and the error, and proceeds without a feasibility verdict for that agent: the verdict guards against an agent that would run over budget, and an agent whose manager refuses its compiles can't run. That manager refuses the agent's turns and writes until a `switchBranch` or `setStrategy` succeeds, and maintenance reports it.
- `previewAgentRuntimeSettings()` no longer throws when an agent's context manager (0.14, required since 0.21.1) refuses its queries, as after its strategy failed to initialize: it answers `{available: false, reason}`, with the agent's live budget and the patch's path, as it does for any preview it can't compute. So `resume()` no longer throws for such an agent either. It warns, naming the agent and the reason, and proceeds without a feasibility verdict for that agent: the verdict guards against an agent that would run over budget, and an agent whose manager refuses its compiles can't run. That manager refuses the agent's turns and writes until a `switchBranch` or `setStrategy` succeeds, and maintenance reports it.
- Offline recovery (`createOfflineRecoveryBranch`) reads a sharded message's body as context-manager 0.14.0 does: a message continues the body before it only when it carries the same group id at a higher shard index. A document posted twice stores two copies under one group id, so an anchor at the end of one copy, or a suppression of one copy alone, is no longer refused. A selection inside one copy is still refused. An anchor at the end of a write that was cut short keeps that partial body and leaves out its retry, which context-manager 0.14.0 reads as a body of its own.
57 changes: 51 additions & 6 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,44 @@ export class Agent {
return result;
}

/**
* The budget compiles plan at, the path a patch's budget takes from it, and
* whether a descent is in flight, from the agent's own state alone: what a
* runtime-settings preview can still say while the context manager refuses
* its queries.
* @internal For the framework's runtime-settings preview; not a host API.
*/
runtimeBudgetPath(patch?: AgentRuntimeSettingsPatch): {
liveBudgetTokens: number;
path: 'immediate' | 'paced' | 'none';
transition: 'stable' | 'converging';
} {
const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
const budget = patch?.contextBudgetTokens;
return {
liveBudgetTokens: live,
path: budget === undefined ? 'none' : budget >= live || patch?.immediate ? 'immediate' : 'paced',
transition: this.contextBudgetTargetTokens === undefined ? 'stable' : 'converging',
};
}

/**
* Whether the context manager answers its queries, as its readiness says.
* It refuses them while its strategy's initialization is pending or has
* failed, until a switchBranch or setStrategy succeeds, and a strategy can
* refuse its readiness on its own, as the autobiographical one does for a
* branch it hasn't reloaded.
* @internal For the framework's runtime-settings preview; not a host API.
*/
contextManagerAnswers(): boolean {
try {
this.contextManager.isReady();
return true;
} catch {
return false;
}
}

/**
* Read-only mirror of `updateRuntimeSettings`' budget semantics: derive what
* the next compile would actually plan at if `patch` were applied, WITHOUT
Expand Down Expand Up @@ -538,9 +576,8 @@ export class Agent {
this.validateRuntimeSettingsPatch(patch);
}
const hot = this.getHotContextSettings();
const live = this.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS;
const { liveBudgetTokens: live, path } = this.runtimeBudgetPath(patch);
const budget = patch?.contextBudgetTokens;
const path = budget === undefined ? 'none' : budget >= live || patch?.immediate ? 'immediate' : 'paced';

const overrides: Record<string, unknown> = {};
if (patch?.tailTokens !== undefined) {
Expand Down Expand Up @@ -757,6 +794,18 @@ export class Agent {
}

private validateRuntimeSettingsPatch(patch: AgentRuntimeSettingsPatch): void {
this.checkRuntimeSettingsPatch(patch);
if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
!this.getHotContextSettings()) {
throw new Error('The active context strategy does not support live tail/transition settings');
}
}

/**
* The checks of a runtime-settings patch that need nothing from the context manager.
* @internal For the framework's runtime-settings preview; not a host API.
*/
checkRuntimeSettingsPatch(patch: AgentRuntimeSettingsPatch): void {
if (Object.keys(patch).length === 0) throw new Error('At least one setting is required');
if (patch.contextBudgetTokens !== undefined) {
if (!Number.isSafeInteger(patch.contextBudgetTokens) || patch.contextBudgetTokens <= this.maxTokens) {
Expand All @@ -776,10 +825,6 @@ export class Agent {
patch.sameRoundThinkTextPolicy !== 'private') {
throw new Error("sameRoundThinkTextPolicy must be 'public' or 'private'");
}
if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
!this.getHotContextSettings()) {
throw new Error('The active context strategy does not support live tail/transition settings');
}
}

/** Settle a converging budget once the window is prepared; true if it settled. */
Expand Down
Loading
Loading