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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ Releases up to and including 0.7.3 predate this file; for their contents see

## Unreleased

### Added

- **Tune-out (#77): subconscious summaries instead of unsubscribing.** A third
channel state between subscribed and gone: `tune_out` diverts a channel's
traffic to a persistent same-model side-agent (participant `Subconscious`)
that summarizes on a cadence in its own voice, judges wakes (addressed
messages and gate-privileged authors, both preconditioned by the resident's
wake gate), and can cancel. Suppressed mentions get a deterministic
`channels/acknowledge` reaction; wake budgets are durable in the
`mcpl/channel-lifecycle` log with max-wakes auto-cancel; optional
`durationSeconds` gives a tune-out a restart-surviving deadline. Cancel
delivers a capped `<tuned-out-backlog>` dump plus a subconscious report;
diverted messages never enter the residents' compiled view (cm `viewFilter`)
and stay excluded after cancel. Standing dispositions ride a fixed
system-position injection on the subconscious's compiles. Requires
`@animalabs/context-manager` with strategy-view composition
(context-manager#54); designer review record in #115.
- **`targetAgents` honored on the channel-incoming fan-out** (was declared but
dead); untargeted events keep the historical broadcast.
- **Per-agent message delivery**: `addMessage(…, {forAgent})` with deferral and
turn-alive guards evaluated against the target agent; gate self-wake notices
deliver to the waking agent. Default path unchanged.

## 0.10.0 — 2026-08-18

Minor release because it adds a third public prose-routing mode and expands the
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"license": "MIT",
"dependencies": {
"@animalabs/chronicle": "^0.3.0",
"@animalabs/context-manager": "^0.6.0",
"@animalabs/context-manager": "github:Meganeuridae/context-manager#feat/strategy-view-composition",
"@animalabs/membrane": "^0.5.78",
"chokidar": "^4.0.3",
"discord.js": "^14.25.1",
Expand All @@ -63,4 +63,4 @@
"engines": {
"node": ">=20.0.0"
}
}
}
398 changes: 371 additions & 27 deletions src/framework.ts

Large diffs are not rendered by default.

31 changes: 28 additions & 3 deletions src/gate/event-gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,13 @@ export class EventGate {

// Dependency-injected callbacks
private emitTrace: (event: TraceEventLike) => void;
private addMessageFn: (participant: string, content: Array<{ type: 'text'; text: string }>, metadata?: Record<string, unknown>) => unknown;
private addMessageFn: (
participant: string,
content: Array<{ type: 'text'; text: string }>,
metadata?: Record<string, unknown>,
/** Registry agent to deliver into; absent = primary (historical). */
forAgent?: string,
) => unknown;
private requestInferenceFn: (agentName: string, reason: string, source: string) => void;
private getAgentNamesFn: () => string[];
/** Clock injection — keeps the new rate_limit / passive_sample paths
Expand All @@ -527,7 +533,12 @@ export class EventGate {
initialConfig?: GateConfig;
privilegedUsersPath?: string;
emitTrace: (event: TraceEventLike) => void;
addMessage: (participant: string, content: Array<{ type: 'text'; text: string }>, metadata?: Record<string, unknown>) => unknown;
addMessage: (
participant: string,
content: Array<{ type: 'text'; text: string }>,
metadata?: Record<string, unknown>,
forAgent?: string,
) => unknown;
requestInference: (agentName: string, reason: string, source: string) => void;
getAgentNames: () => string[];
/** Optional clock — defaults to Date.now. Tests inject for deterministic time. */
Expand Down Expand Up @@ -1582,10 +1593,13 @@ export class EventGate {
// before the inference request so it rides the wake turn (addMessage's
// turn-alive guard defers it to that turn's start if needed).
const nowIso = new Date(this.now()).toISOString().slice(0, 19) + 'Z';
// Deliver the notice into the WAKING agent's window — a self-wake
// armed by a non-primary agent (e.g. a subconscious cadence) must not
// narrate itself into the primary's context.
this.addMessageFn('user', [{
type: 'text',
text: `[self-wake] your ${source} timer (${Math.round(ms / 1000)}s) elapsed — now ${nowIso}`,
}], { source: 'gate:self-wake' });
}], { source: 'gate:self-wake' }, agentName);
this.requestInferenceFn(
agentName,
`self-scheduled wake (${source}, ${Math.round(ms / 1000)}s)`,
Expand Down Expand Up @@ -1659,6 +1673,17 @@ export class EventGate {
}

/** Load the privileged-users file (bare array or { userIds: [...] }). */
/**
* Whether an author id is on the privileged-users list (hot-reloaded).
* Shared by sleep bypass and tune-out wake classification (#77: wakes are
* "mentions, gate-privileged authors, etc.").
*/
isPrivilegedUser(authorId: string | null | undefined): boolean {
if (!authorId) return false;
this.loadPrivileged();
return this.privilegedUserIds.has(authorId);
}

private loadPrivileged(): void {
if (!this.privilegedUsersPath) return;
try {
Expand Down
Loading