@haaaiawd/second-nature 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/cli/commands/connector-init.d.ts +19 -0
- package/runtime/cli/commands/connector-init.js +168 -0
- package/runtime/cli/commands/connector-status.d.ts +12 -0
- package/runtime/cli/commands/connector-status.js +156 -0
- package/runtime/cli/commands/index.js +40 -0
- package/runtime/cli/index.js +52 -0
- package/runtime/cli/ops/ops-router.d.ts +5 -0
- package/runtime/cli/ops/ops-router.js +34 -0
- package/runtime/cli/ops/workspace-heartbeat-runner.js +22 -0
- package/runtime/connectors/agent-network/agent-world/adapter.d.ts +11 -0
- package/runtime/connectors/agent-network/agent-world/adapter.js +58 -0
- package/runtime/connectors/agent-network/agent-world/index.d.ts +2 -0
- package/runtime/connectors/agent-network/agent-world/index.js +2 -0
- package/runtime/connectors/agent-network/agent-world/manifest.d.ts +2 -0
- package/runtime/connectors/agent-network/agent-world/manifest.js +7 -0
- package/runtime/connectors/base/manifest.d.ts +13 -0
- package/runtime/connectors/base/manifest.js +47 -0
- package/runtime/connectors/manifest/manifest-parser.d.ts +16 -0
- package/runtime/connectors/manifest/manifest-parser.js +35 -0
- package/runtime/connectors/manifest/manifest-schema.d.ts +145 -0
- package/runtime/connectors/manifest/manifest-schema.js +51 -0
- package/runtime/connectors/registry/dynamic-connector-registry.d.ts +29 -0
- package/runtime/connectors/registry/dynamic-connector-registry.js +123 -0
- package/runtime/connectors/registry/index.d.ts +3 -0
- package/runtime/connectors/registry/index.js +3 -0
- package/runtime/connectors/registry/manifest-scanner.d.ts +9 -0
- package/runtime/connectors/registry/manifest-scanner.js +29 -0
- package/runtime/connectors/registry/trust-policy.d.ts +13 -0
- package/runtime/connectors/registry/trust-policy.js +37 -0
- package/runtime/connectors/services/connector-executor-adapter.js +49 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +3 -0
- package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +52 -1
- package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +3 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +19 -0
- package/runtime/core/second-nature/orchestrator/goal-priority.js +67 -0
- package/runtime/core/second-nature/orchestrator/intent-planner.js +8 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.d.ts +27 -0
- package/runtime/core/second-nature/orchestrator/narrative-update.js +107 -0
- package/runtime/core/second-nature/types.d.ts +4 -0
- package/runtime/guidance/draft-narrative-outreach.d.ts +36 -0
- package/runtime/guidance/draft-narrative-outreach.js +84 -0
- package/runtime/guidance/index.d.ts +1 -0
- package/runtime/guidance/index.js +1 -0
- package/runtime/guidance/outreach-draft-schema.d.ts +3 -3
- package/runtime/observability/connector-inventory-ledger.d.ts +45 -0
- package/runtime/observability/connector-inventory-ledger.js +72 -0
- package/runtime/observability/db/index.js +13 -0
- package/runtime/observability/db/schema/connector-inventory.d.ts +174 -0
- package/runtime/observability/db/schema/connector-inventory.js +15 -0
- package/runtime/observability/db/schema/index.d.ts +1 -0
- package/runtime/observability/db/schema/index.js +1 -0
- package/runtime/storage/chronicle/session-chronicle-store.d.ts +42 -0
- package/runtime/storage/chronicle/session-chronicle-store.js +66 -0
- package/runtime/storage/db/index.js +75 -0
- package/runtime/storage/db/schema/agent-goal.d.ts +235 -0
- package/runtime/storage/db/schema/agent-goal.js +19 -0
- package/runtime/storage/db/schema/index.d.ts +5 -0
- package/runtime/storage/db/schema/index.js +5 -0
- package/runtime/storage/db/schema/memory-store.d.ts +199 -0
- package/runtime/storage/db/schema/memory-store.js +18 -0
- package/runtime/storage/db/schema/narrative-state.d.ts +195 -0
- package/runtime/storage/db/schema/narrative-state.js +16 -0
- package/runtime/storage/db/schema/relationship-memory.d.ts +174 -0
- package/runtime/storage/db/schema/relationship-memory.js +14 -0
- package/runtime/storage/db/schema/session-chronicle.d.ts +199 -0
- package/runtime/storage/db/schema/session-chronicle.js +18 -0
- package/runtime/storage/goal/agent-goal-store.d.ts +57 -0
- package/runtime/storage/goal/agent-goal-store.js +109 -0
- package/runtime/storage/index.d.ts +5 -0
- package/runtime/storage/index.js +5 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.d.ts +70 -0
- package/runtime/storage/memory-store/memory-store-lifecycle.js +113 -0
- package/runtime/storage/narrative/narrative-state-store.d.ts +40 -0
- package/runtime/storage/narrative/narrative-state-store.js +79 -0
- package/runtime/storage/relationship/relationship-memory-store.d.ts +42 -0
- package/runtime/storage/relationship/relationship-memory-store.js +76 -0
- package/workspace-ops-bridge.js +1 -0
|
@@ -5,8 +5,10 @@ import { createConnectorPolicyLayer } from "../base/policy-layer.js";
|
|
|
5
5
|
import { InMemoryEffectCommitLedger } from "../base/execution-policy.js";
|
|
6
6
|
import { moltbookManifest } from "../social-community/moltbook/manifest.js";
|
|
7
7
|
import { evomapManifest } from "../agent-network/evomap/manifest.js";
|
|
8
|
+
import { agentWorldManifest } from "../agent-network/agent-world/manifest.js";
|
|
8
9
|
import { createMoltbookApiClient } from "../social-community/moltbook/api-client.js";
|
|
9
10
|
import { createMoltbookRunner } from "../social-community/moltbook/adapter.js";
|
|
11
|
+
import { createAgentWorldRunner } from "../agent-network/agent-world/adapter.js";
|
|
10
12
|
import { ExecutionTelemetry } from "../../observability/services/execution-telemetry.js";
|
|
11
13
|
import { createCredentialVault } from "../../storage/services/credential-vault.js";
|
|
12
14
|
import { createCredentialRouteContextPort } from "./credential-route-context.js";
|
|
@@ -74,6 +76,52 @@ function createAdaptiveExecutionRunner(vault) {
|
|
|
74
76
|
},
|
|
75
77
|
};
|
|
76
78
|
}
|
|
79
|
+
if (platformId === "agent-world") {
|
|
80
|
+
const baseUrl = process.env.SECOND_NATURE_AGENT_WORLD_BASE_URL;
|
|
81
|
+
if (!baseUrl) {
|
|
82
|
+
return {
|
|
83
|
+
platformId,
|
|
84
|
+
channel: request.preferredChannel ?? "api_rest",
|
|
85
|
+
latencyMs: Date.now() - started,
|
|
86
|
+
success: false,
|
|
87
|
+
error: {
|
|
88
|
+
code: "configuration_missing",
|
|
89
|
+
detail: "SECOND_NATURE_AGENT_WORLD_BASE_URL not set",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const runner = createAgentWorldRunner({
|
|
94
|
+
apiClient: {
|
|
95
|
+
async readFeed(payload, _apiKey) {
|
|
96
|
+
const resp = await fetch(`${baseUrl}/api/v1/feed`, {
|
|
97
|
+
headers: { "Authorization": `Bearer ${_apiKey}`, "Content-Type": "application/json" },
|
|
98
|
+
});
|
|
99
|
+
if (!resp.ok)
|
|
100
|
+
throw { code: "api_error", detail: `agent-world feed: ${resp.status}` };
|
|
101
|
+
return resp.json();
|
|
102
|
+
},
|
|
103
|
+
async discoverWork(payload, _apiKey) {
|
|
104
|
+
const resp = await fetch(`${baseUrl}/api/v1/work`, {
|
|
105
|
+
headers: { "Authorization": `Bearer ${_apiKey}`, "Content-Type": "application/json" },
|
|
106
|
+
});
|
|
107
|
+
if (!resp.ok)
|
|
108
|
+
throw { code: "api_error", detail: `agent-world work: ${resp.status}` };
|
|
109
|
+
return resp.json();
|
|
110
|
+
},
|
|
111
|
+
async claimTask(payload, _apiKey) {
|
|
112
|
+
const resp = await fetch(`${baseUrl}/api/v1/tasks/${payload.taskId ?? "unknown"}/claim`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Authorization": `Bearer ${_apiKey}`, "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify(payload),
|
|
116
|
+
});
|
|
117
|
+
if (!resp.ok)
|
|
118
|
+
throw { code: "api_error", detail: `agent-world claim: ${resp.status}` };
|
|
119
|
+
return resp.json();
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
return runner.run(_plan, request);
|
|
124
|
+
}
|
|
77
125
|
return {
|
|
78
126
|
platformId,
|
|
79
127
|
channel: request.preferredChannel ?? "api_rest",
|
|
@@ -92,6 +140,7 @@ export function createConnectorExecutorAdapter(options) {
|
|
|
92
140
|
const registry = new CapabilityContractRegistry();
|
|
93
141
|
registry.register({ ...moltbookManifest });
|
|
94
142
|
registry.register({ ...evomapManifest });
|
|
143
|
+
registry.register({ ...agentWorldManifest });
|
|
95
144
|
const routeContextPort = createCredentialRouteContextPort(vault);
|
|
96
145
|
const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
|
|
97
146
|
const telemetry = new ExecutionTelemetry(options.observabilityDb);
|
|
@@ -20,6 +20,7 @@ import type { GuidanceDraftPort } from "../../../guidance/outreach-draft-schema.
|
|
|
20
20
|
import type { StateDatabase } from "../../../storage/db/index.js";
|
|
21
21
|
import { type OpenClawDeliveryPort } from "../outreach/dispatch-user-outreach.js";
|
|
22
22
|
import type { ConnectorExecutor } from "../../../connectors/base/contract.js";
|
|
23
|
+
import type { NarrativeStateStore } from "../../../storage/narrative/narrative-state-store.js";
|
|
23
24
|
export interface HeartbeatDecisionTracePayload {
|
|
24
25
|
scope: RuntimeScope;
|
|
25
26
|
status: HeartbeatCycleStatus;
|
|
@@ -58,6 +59,8 @@ export interface HeartbeatDeps {
|
|
|
58
59
|
* through the connector-system instead of returning connector_dispatch_unwired.
|
|
59
60
|
*/
|
|
60
61
|
connectorExecutor?: ConnectorExecutor;
|
|
62
|
+
/** T2.1.5: when present, heartbeat writes a source-backed NarrativeState revision after each cycle. */
|
|
63
|
+
narrativeStateStore?: NarrativeStateStore;
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
63
66
|
* Ingest a heartbeat rhythm signal and drive one full decision round.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { buildContinuitySnapshot, } from "./snapshot-builder.js";
|
|
2
2
|
import { buildHeartbeatRuntimeSnapshot, } from "./runtime-snapshot.js";
|
|
3
3
|
import { planCandidateIntents } from "../orchestrator/intent-planner.js";
|
|
4
|
+
import { applyGoalPriority } from "../orchestrator/goal-priority.js";
|
|
4
5
|
import { evaluateHardGuards } from "../orchestrator/guard-layer.js";
|
|
5
6
|
import { dispatchUserOutreachIntent, } from "../outreach/dispatch-user-outreach.js";
|
|
6
7
|
import { buildJudgeOutreachInputFromSnapshot } from "../outreach/judge-input-from-snapshot.js";
|
|
7
8
|
import { runSourceBackedQuiet } from "../quiet/run-source-backed-quiet.js";
|
|
8
9
|
import { toCapabilityIntent } from "../orchestrator/effect-dispatcher.js";
|
|
10
|
+
import { updateNarrativeAfterEffect } from "../orchestrator/narrative-update.js";
|
|
9
11
|
/**
|
|
10
12
|
* Resolves the heartbeat outcome for a guard-allowed intent (outreach dispatch, quiet orchestration, or default).
|
|
11
13
|
* Exported for unit tests (CR-M1 wiring).
|
|
@@ -83,6 +85,49 @@ export async function resolveAllowedIntentResult(intent, runtime, inputs, signal
|
|
|
83
85
|
reasons,
|
|
84
86
|
};
|
|
85
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* T2.1.5: after the cycle result is known, write a narrative revision when
|
|
90
|
+
* a NarrativeStateStore is wired. Errors are swallowed so the cycle result
|
|
91
|
+
* is never blocked by a store failure. Store failures are optionally traced
|
|
92
|
+
* via recordDecisionTrace so operators can monitor store health.
|
|
93
|
+
*/
|
|
94
|
+
async function maybeUpdateNarrativeState(result, selectedIntent, runtime, store, recordTrace, signal) {
|
|
95
|
+
if (!store)
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
const prior = await store.loadNarrativeState();
|
|
99
|
+
const update = updateNarrativeAfterEffect({
|
|
100
|
+
result,
|
|
101
|
+
selectedIntent,
|
|
102
|
+
lifeEvidence: runtime.lifeEvidence,
|
|
103
|
+
priorNarrative: prior,
|
|
104
|
+
});
|
|
105
|
+
await store.updateNarrativeState(update);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// degrade silently; narrative update is best-effort
|
|
109
|
+
if (recordTrace && signal) {
|
|
110
|
+
try {
|
|
111
|
+
await recordTrace({
|
|
112
|
+
scope: result.scope,
|
|
113
|
+
status: result.status,
|
|
114
|
+
reasons: ["narrative_update_failed"],
|
|
115
|
+
selectedIntentId: selectedIntent?.id,
|
|
116
|
+
rhythmWindowId: runtime.rhythmWindow.windowId,
|
|
117
|
+
allowedIntentKinds: [...runtime.rhythmWindow.allowedIntentKinds],
|
|
118
|
+
candidateCount: 0,
|
|
119
|
+
lifeEvidenceEmpty: runtime.lifeEvidence.evidenceRefs.length === 0 &&
|
|
120
|
+
runtime.lifeEvidence.platformEventCount === 0 &&
|
|
121
|
+
runtime.lifeEvidence.workEventCount === 0,
|
|
122
|
+
trigger: signal.trigger,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// trace emission must also not block the cycle
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
86
131
|
/**
|
|
87
132
|
* Ingest a heartbeat rhythm signal and drive one full decision round.
|
|
88
133
|
*/
|
|
@@ -91,7 +136,8 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
91
136
|
const snapshot = buildContinuitySnapshot(inputs);
|
|
92
137
|
const timestamp = signal.payload.timestamp;
|
|
93
138
|
const runtime = buildHeartbeatRuntimeSnapshot(timestamp, inputs, snapshot);
|
|
94
|
-
const
|
|
139
|
+
const rawCandidates = planCandidateIntents(runtime);
|
|
140
|
+
const { candidates } = applyGoalPriority(rawCandidates, inputs.acceptedGoals);
|
|
95
141
|
const emitTrace = async (result) => {
|
|
96
142
|
if (!deps.recordDecisionTrace)
|
|
97
143
|
return;
|
|
@@ -132,6 +178,7 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
132
178
|
? { ...resolved, reasons: evaluation.reasons }
|
|
133
179
|
: resolved;
|
|
134
180
|
await emitTrace(result);
|
|
181
|
+
await maybeUpdateNarrativeState(result, intent, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
|
|
135
182
|
return result;
|
|
136
183
|
}
|
|
137
184
|
if (evaluation.verdict === "defer") {
|
|
@@ -149,6 +196,7 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
149
196
|
reasons: ["silent_no_candidates"],
|
|
150
197
|
};
|
|
151
198
|
await emitTrace(result);
|
|
199
|
+
await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
|
|
152
200
|
return result;
|
|
153
201
|
}
|
|
154
202
|
if (!anyAllow && anyDefer && !anyDeny) {
|
|
@@ -158,6 +206,7 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
158
206
|
reasons: denyReasons.length > 0 ? denyReasons : ["all_candidates_deferred"],
|
|
159
207
|
};
|
|
160
208
|
await emitTrace(result);
|
|
209
|
+
await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
|
|
161
210
|
return result;
|
|
162
211
|
}
|
|
163
212
|
if (!anyAllow && denyReasons.length > 0) {
|
|
@@ -167,6 +216,7 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
167
216
|
reasons: denyReasons,
|
|
168
217
|
};
|
|
169
218
|
await emitTrace(result);
|
|
219
|
+
await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
|
|
170
220
|
return result;
|
|
171
221
|
}
|
|
172
222
|
const result = {
|
|
@@ -175,6 +225,7 @@ export async function ingestRhythmSignal(signal, deps) {
|
|
|
175
225
|
reasons: ["no_allow_verdict"],
|
|
176
226
|
};
|
|
177
227
|
await emitTrace(result);
|
|
228
|
+
await maybeUpdateNarrativeState(result, undefined, runtime, deps.narrativeStateStore, deps.recordDecisionTrace, signal);
|
|
178
229
|
return result;
|
|
179
230
|
}
|
|
180
231
|
/**
|
|
@@ -10,6 +10,7 @@ import type { ContinuitySnapshot, ControlPlaneSourceRef, TopLevelMode } from "..
|
|
|
10
10
|
import type { RhythmPolicy } from "../rhythm/rhythm-policy.js";
|
|
11
11
|
import type { DeliveryCapabilitySnapshot } from "../outreach/delivery-target.js";
|
|
12
12
|
import type { UserInterestSnapshot } from "../../../storage/user-interest/types.js";
|
|
13
|
+
import type { AgentGoal } from "../../../storage/goal/agent-goal-store.js";
|
|
13
14
|
export interface SnapshotInputs {
|
|
14
15
|
mode: TopLevelMode;
|
|
15
16
|
currentWindowId: string;
|
|
@@ -41,6 +42,8 @@ export interface SnapshotInputs {
|
|
|
41
42
|
deliveryCapability?: DeliveryCapabilitySnapshot;
|
|
42
43
|
/** When present, outreach judgment uses this user-interest read model (T4.2.2). */
|
|
43
44
|
userInterestSnapshot?: UserInterestSnapshot;
|
|
45
|
+
/** T2.1.4: accepted goals to influence candidate intent priority. */
|
|
46
|
+
acceptedGoals?: AgentGoal[];
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
46
49
|
* Build a ContinuitySnapshot from loaded inputs.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T2.1.4 — Goal-Directed Intent Priority.
|
|
3
|
+
*
|
|
4
|
+
* `applyGoalPriority` adjusts candidate intent priorities based on accepted AgentGoals.
|
|
5
|
+
* Priority order: user_task > accepted_goal > rhythm.
|
|
6
|
+
* Only goals with status === "accepted" and origin !== "agent_proposed" are considered.
|
|
7
|
+
* All other statuses (proposal / rejected / completed / paused) are implicitly excluded.
|
|
8
|
+
*/
|
|
9
|
+
import type { CandidateIntent } from "../types.js";
|
|
10
|
+
import type { AgentGoal } from "../../../storage/goal/agent-goal-store.js";
|
|
11
|
+
export interface ApplyGoalPriorityResult {
|
|
12
|
+
candidates: CandidateIntent[];
|
|
13
|
+
goalInfluences: Array<{
|
|
14
|
+
candidateId: string;
|
|
15
|
+
goalIds: string[];
|
|
16
|
+
boost: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export declare function applyGoalPriority(candidates: CandidateIntent[], goals: AgentGoal[] | undefined): ApplyGoalPriorityResult;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-goal priority boost applied when an accepted goal matches a candidate.
|
|
3
|
+
*
|
|
4
|
+
* Rationale: +20 per goal keeps a priority-50 candidate under 200
|
|
5
|
+
* even with 7 matching goals (50 + 140 = 190). Planner baselines
|
|
6
|
+
* range from 40–100, so 200 provides ample headroom without overflow.
|
|
7
|
+
*/
|
|
8
|
+
const GOAL_PRIORITY_BOOST = 20;
|
|
9
|
+
function isGoalRelatedToCandidate(goal, candidate) {
|
|
10
|
+
if (!candidate.platformId)
|
|
11
|
+
return false;
|
|
12
|
+
const goalText = `${goal.description} ${goal.completionCriteria}`.toLowerCase();
|
|
13
|
+
const platformId = candidate.platformId.toLowerCase();
|
|
14
|
+
// Direct platformId mention in goal text
|
|
15
|
+
if (goalText.includes(platformId))
|
|
16
|
+
return true;
|
|
17
|
+
// Goal description contains candidate summary keywords
|
|
18
|
+
const summaryWords = candidate.summary.toLowerCase().split(/\s+/);
|
|
19
|
+
for (const word of summaryWords) {
|
|
20
|
+
if (word.length > 3 && goalText.includes(word))
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export function applyGoalPriority(candidates, goals) {
|
|
26
|
+
const acceptedGoals = (goals ?? []).filter((g) => g.status === "accepted" && g.origin !== "agent_proposed");
|
|
27
|
+
if (acceptedGoals.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
candidates: candidates.map((c) => ({
|
|
30
|
+
...c,
|
|
31
|
+
priorityReasons: c.priorityReasons ?? ["rhythm"],
|
|
32
|
+
})),
|
|
33
|
+
goalInfluences: [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const influences = [];
|
|
37
|
+
const adjusted = candidates.map((candidate) => {
|
|
38
|
+
const relatedGoals = acceptedGoals.filter((g) => isGoalRelatedToCandidate(g, candidate));
|
|
39
|
+
if (relatedGoals.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
...candidate,
|
|
42
|
+
priorityReasons: candidate.priorityReasons ?? ["rhythm"],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const boost = GOAL_PRIORITY_BOOST * relatedGoals.length;
|
|
46
|
+
const goalIds = relatedGoals.map((g) => g.goalId);
|
|
47
|
+
influences.push({
|
|
48
|
+
candidateId: candidate.id,
|
|
49
|
+
goalIds,
|
|
50
|
+
boost,
|
|
51
|
+
});
|
|
52
|
+
const reasons = [
|
|
53
|
+
...(candidate.priorityReasons ?? ["rhythm"]),
|
|
54
|
+
...relatedGoals.map((g) => `goal_boost:${g.goalId}`),
|
|
55
|
+
];
|
|
56
|
+
return {
|
|
57
|
+
...candidate,
|
|
58
|
+
priority: candidate.priority + boost,
|
|
59
|
+
goalInfluenceRefs: goalIds,
|
|
60
|
+
priorityReasons: reasons,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
candidates: adjusted.sort((a, b) => b.priority - a.priority),
|
|
65
|
+
goalInfluences: influences,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -34,6 +34,7 @@ function planWorkIntents(runtime) {
|
|
|
34
34
|
effectClass: "connector_action",
|
|
35
35
|
sourceRefs: [...OBLIGATION_SOURCE],
|
|
36
36
|
idempotencyKey: `obligation:${obligation}:${index}`,
|
|
37
|
+
goalInfluenceRefs: [],
|
|
37
38
|
}));
|
|
38
39
|
}
|
|
39
40
|
function planExplorationIntents(runtime) {
|
|
@@ -50,6 +51,7 @@ function planExplorationIntents(runtime) {
|
|
|
50
51
|
effectClass: "connector_action",
|
|
51
52
|
sourceRefs: refs,
|
|
52
53
|
idempotencyKey: "exploration:scan platform opportunities",
|
|
54
|
+
goalInfluenceRefs: [],
|
|
53
55
|
},
|
|
54
56
|
];
|
|
55
57
|
}
|
|
@@ -67,6 +69,7 @@ function planSocialIntents(runtime) {
|
|
|
67
69
|
effectClass: "connector_action",
|
|
68
70
|
sourceRefs: refs,
|
|
69
71
|
idempotencyKey: "social:engage social platforms",
|
|
72
|
+
goalInfluenceRefs: [],
|
|
70
73
|
},
|
|
71
74
|
];
|
|
72
75
|
}
|
|
@@ -85,6 +88,7 @@ function planQuietReflectionIntents(runtime) {
|
|
|
85
88
|
effectClass: "no_effect",
|
|
86
89
|
sourceRefs: [],
|
|
87
90
|
idempotencyKey: "quiet:bookkeeping",
|
|
91
|
+
goalInfluenceRefs: [],
|
|
88
92
|
});
|
|
89
93
|
}
|
|
90
94
|
if (isAllowedKind("maintenance", runtime)) {
|
|
@@ -97,6 +101,7 @@ function planQuietReflectionIntents(runtime) {
|
|
|
97
101
|
effectClass: "maintenance",
|
|
98
102
|
sourceRefs: [],
|
|
99
103
|
idempotencyKey: "maintenance:checks",
|
|
104
|
+
goalInfluenceRefs: [],
|
|
100
105
|
});
|
|
101
106
|
}
|
|
102
107
|
if (isAllowedKind("reflection", runtime)) {
|
|
@@ -110,6 +115,7 @@ function planQuietReflectionIntents(runtime) {
|
|
|
110
115
|
effectClass: "narrative_reflection",
|
|
111
116
|
sourceRefs: refs,
|
|
112
117
|
idempotencyKey: "reflection:narrative",
|
|
118
|
+
goalInfluenceRefs: [],
|
|
113
119
|
});
|
|
114
120
|
}
|
|
115
121
|
return out;
|
|
@@ -131,6 +137,7 @@ function planOutreachIntents(runtime) {
|
|
|
131
137
|
effectClass: "user_outreach",
|
|
132
138
|
sourceRefs: refs,
|
|
133
139
|
idempotencyKey: "outreach:consider proactive user outreach",
|
|
140
|
+
goalInfluenceRefs: [],
|
|
134
141
|
},
|
|
135
142
|
];
|
|
136
143
|
}
|
|
@@ -149,6 +156,7 @@ export function planCandidateIntents(runtime) {
|
|
|
149
156
|
effectClass: "maintenance",
|
|
150
157
|
sourceRefs: [],
|
|
151
158
|
idempotencyKey: "maintenance:checks",
|
|
159
|
+
goalInfluenceRefs: [],
|
|
152
160
|
},
|
|
153
161
|
];
|
|
154
162
|
return pausedMaintenance
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T2.1.5 — NarrativeState update after heartbeat effect/fallback.
|
|
3
|
+
*
|
|
4
|
+
* Produces a source-backed narrative revision or an honest empty-state
|
|
5
|
+
* (`awaiting_sources` / `insufficient_sources`) based on the cycle result.
|
|
6
|
+
*
|
|
7
|
+
* Rules-only implementation; no live LLM.
|
|
8
|
+
*/
|
|
9
|
+
import type { HeartbeatCycleResult } from "../heartbeat/signal.js";
|
|
10
|
+
import type { CandidateIntent } from "../types.js";
|
|
11
|
+
import type { PlannerLifeEvidenceSlice } from "../heartbeat/runtime-snapshot.js";
|
|
12
|
+
import type { NarrativeState, NarrativeStateUpdate } from "../../../storage/narrative/narrative-state-store.js";
|
|
13
|
+
export interface UpdateNarrativeAfterEffectInput {
|
|
14
|
+
result: HeartbeatCycleResult;
|
|
15
|
+
selectedIntent?: CandidateIntent;
|
|
16
|
+
lifeEvidence: PlannerLifeEvidenceSlice;
|
|
17
|
+
priorNarrative?: NarrativeState | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the next NarrativeState revision from a completed heartbeat cycle.
|
|
21
|
+
*
|
|
22
|
+
* - `intent_selected` + sources → `active`, focus/progress updated.
|
|
23
|
+
* - `intent_selected` without sources → `awaiting_sources`, claim recorded as unsupported.
|
|
24
|
+
* - No action (heartbeat_ok / denied / deferred / etc.) → preserve prior state or
|
|
25
|
+
* seed an empty `awaiting_sources` state when no prior exists.
|
|
26
|
+
*/
|
|
27
|
+
export declare function updateNarrativeAfterEffect(input: UpdateNarrativeAfterEffectInput): NarrativeStateUpdate;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const MAX_PROGRESS_ENTRIES = 10;
|
|
2
|
+
const DEFAULT_NARRATIVE_ID = "default";
|
|
3
|
+
function mapControlPlaneRefToSourceRef(ref) {
|
|
4
|
+
return {
|
|
5
|
+
sourceId: ref.id,
|
|
6
|
+
kind: ref.kind,
|
|
7
|
+
url: ref.uri,
|
|
8
|
+
snippet: ref.excerptHash,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compute narrative confidence based on source evidence.
|
|
13
|
+
*
|
|
14
|
+
* Formula: min(intentSources / 3, 1) + (lifeEvidenceSources > 0 ? 0.1 : 0)
|
|
15
|
+
*
|
|
16
|
+
* Rationale:
|
|
17
|
+
* - Base: 1/3 per intent source (3 sources = 100% confidence)
|
|
18
|
+
* - Boost: +0.1 if any life evidence exists (signals corroboration)
|
|
19
|
+
* - Capped at 1.0 (100%)
|
|
20
|
+
*/
|
|
21
|
+
function computeConfidence(intentSources, lifeEvidenceSources) {
|
|
22
|
+
if (intentSources === 0 && lifeEvidenceSources === 0)
|
|
23
|
+
return 0;
|
|
24
|
+
const base = Math.min(intentSources / 3, 1);
|
|
25
|
+
const boost = lifeEvidenceSources > 0 ? 0.1 : 0;
|
|
26
|
+
return Math.min(base + boost, 1);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the next NarrativeState revision from a completed heartbeat cycle.
|
|
30
|
+
*
|
|
31
|
+
* - `intent_selected` + sources → `active`, focus/progress updated.
|
|
32
|
+
* - `intent_selected` without sources → `awaiting_sources`, claim recorded as unsupported.
|
|
33
|
+
* - No action (heartbeat_ok / denied / deferred / etc.) → preserve prior state or
|
|
34
|
+
* seed an empty `awaiting_sources` state when no prior exists.
|
|
35
|
+
*/
|
|
36
|
+
export function updateNarrativeAfterEffect(input) {
|
|
37
|
+
const { result, selectedIntent, lifeEvidence, priorNarrative } = input;
|
|
38
|
+
const now = new Date().toISOString();
|
|
39
|
+
const prior = priorNarrative ?? null;
|
|
40
|
+
const narrativeId = prior?.narrativeId ?? DEFAULT_NARRATIVE_ID;
|
|
41
|
+
const nextRevision = (prior?.revision ?? 0) + 1;
|
|
42
|
+
// --- intent_selected branch ------------------------------------------------
|
|
43
|
+
if (result.status === "intent_selected" && selectedIntent) {
|
|
44
|
+
const hasIntentSources = selectedIntent.sourceRefs.length > 0;
|
|
45
|
+
const hasLifeEvidence = !(lifeEvidence.evidenceRefs.length === 0 &&
|
|
46
|
+
lifeEvidence.platformEventCount === 0 &&
|
|
47
|
+
lifeEvidence.workEventCount === 0);
|
|
48
|
+
const sourceRefs = selectedIntent.sourceRefs.map(mapControlPlaneRefToSourceRef);
|
|
49
|
+
if (hasIntentSources || hasLifeEvidence) {
|
|
50
|
+
// Source-backed revision
|
|
51
|
+
const progressEntry = `${selectedIntent.effectClass}: ${selectedIntent.summary}`;
|
|
52
|
+
const progress = [...(prior?.progress ?? [])];
|
|
53
|
+
if (!progress.includes(progressEntry)) {
|
|
54
|
+
progress.push(progressEntry);
|
|
55
|
+
}
|
|
56
|
+
const boundedProgress = progress.slice(-MAX_PROGRESS_ENTRIES);
|
|
57
|
+
return {
|
|
58
|
+
narrativeId,
|
|
59
|
+
revision: nextRevision,
|
|
60
|
+
focus: selectedIntent.summary,
|
|
61
|
+
progress: boundedProgress,
|
|
62
|
+
nextIntent: "continue",
|
|
63
|
+
confidence: computeConfidence(selectedIntent.sourceRefs.length, lifeEvidence.evidenceRefs.length),
|
|
64
|
+
sourceRefs,
|
|
65
|
+
unsupportedClaims: [],
|
|
66
|
+
status: "active",
|
|
67
|
+
updatedAt: now,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// No sources → awaiting_sources
|
|
71
|
+
return {
|
|
72
|
+
narrativeId,
|
|
73
|
+
revision: nextRevision,
|
|
74
|
+
focus: prior?.focus ?? "awaiting_evidence",
|
|
75
|
+
progress: prior?.progress ?? [],
|
|
76
|
+
nextIntent: "await_sources",
|
|
77
|
+
confidence: 0,
|
|
78
|
+
sourceRefs: [],
|
|
79
|
+
unsupportedClaims: [selectedIntent.summary],
|
|
80
|
+
status: "awaiting_sources",
|
|
81
|
+
updatedAt: now,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// --- No action / fallback branches -----------------------------------------
|
|
85
|
+
if (prior) {
|
|
86
|
+
// Preserve existing state, bump revision and timestamp so observers
|
|
87
|
+
// know the heartbeat cycle ran even when no intent was selected.
|
|
88
|
+
return {
|
|
89
|
+
...prior,
|
|
90
|
+
revision: nextRevision,
|
|
91
|
+
updatedAt: now,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// No prior state and no action → seed empty awaiting_sources state
|
|
95
|
+
return {
|
|
96
|
+
narrativeId,
|
|
97
|
+
revision: nextRevision,
|
|
98
|
+
focus: "awaiting_evidence",
|
|
99
|
+
progress: [],
|
|
100
|
+
nextIntent: "await_sources",
|
|
101
|
+
confidence: 0,
|
|
102
|
+
sourceRefs: [],
|
|
103
|
+
unsupportedClaims: [],
|
|
104
|
+
status: "awaiting_sources",
|
|
105
|
+
updatedAt: now,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -41,6 +41,10 @@ export interface CandidateIntent {
|
|
|
41
41
|
sourceRefs: ControlPlaneSourceRef[];
|
|
42
42
|
/** Dedupe / cooldown key; defaults to stable fingerprint in guard layer when omitted. */
|
|
43
43
|
idempotencyKey?: string;
|
|
44
|
+
/** T2.1.4: IDs of accepted AgentGoals that influenced this candidate's priority. */
|
|
45
|
+
goalInfluenceRefs?: string[];
|
|
46
|
+
/** T2.1.4: Human-readable reasons for the priority value (goal influence, user task, rhythm). */
|
|
47
|
+
priorityReasons?: string[];
|
|
44
48
|
}
|
|
45
49
|
export interface GuardEvaluation {
|
|
46
50
|
verdict: GuardVerdict;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T6.1.1 — Narrative Outreach Draft generator.
|
|
3
|
+
*
|
|
4
|
+
* Produces source-backed outreach drafts from evidence + narrative + relationship.
|
|
5
|
+
* Rules-only implementation (no live LLM); honors grounding, redaction, and insufficient_history.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This function assumes narrativeState.focus / nextIntent and
|
|
8
|
+
* relationshipMemory fields are pre-redacted by upstream state-system.
|
|
9
|
+
* No additional PII filtering is applied here — defense-in-depth is
|
|
10
|
+
* the responsibility of the NarrativeState / RelationshipMemory stores.
|
|
11
|
+
*/
|
|
12
|
+
import type { NarrativeState, SourceRef } from "../storage/narrative/narrative-state-store.js";
|
|
13
|
+
import type { RelationshipMemory } from "../storage/relationship/relationship-memory-store.js";
|
|
14
|
+
export interface NarrativeOutreachDraftRequest {
|
|
15
|
+
evidenceRefs: SourceRef[];
|
|
16
|
+
narrativeState?: NarrativeState;
|
|
17
|
+
relationshipMemory?: RelationshipMemory;
|
|
18
|
+
platformId?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface GroundingReport {
|
|
21
|
+
status: "grounded" | "degraded" | "blocked";
|
|
22
|
+
sourceCoverage: number;
|
|
23
|
+
unsupportedClaims: string[];
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
export interface NarrativeOutreachDraftResult {
|
|
27
|
+
draft: {
|
|
28
|
+
whatHappened: string;
|
|
29
|
+
whyItMatters: string;
|
|
30
|
+
sourceRefs: SourceRef[];
|
|
31
|
+
tone: "friend" | "insufficient_history" | "blocked";
|
|
32
|
+
};
|
|
33
|
+
groundingReport: GroundingReport;
|
|
34
|
+
promptVersion: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function draftNarrativeOutreach(request: NarrativeOutreachDraftRequest): NarrativeOutreachDraftResult;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
function computeSourceCoverage(claims, _evidenceRefs, narrativeSourceRefs) {
|
|
2
|
+
if (claims.length === 0)
|
|
3
|
+
return { coverage: 1, unsupported: [] };
|
|
4
|
+
// Only narrative-level source refs count toward coverage.
|
|
5
|
+
const count = narrativeSourceRefs?.length ?? 0;
|
|
6
|
+
const coverage = Math.min(count / claims.length, 1);
|
|
7
|
+
return {
|
|
8
|
+
coverage,
|
|
9
|
+
unsupported: coverage >= 1 ? [] : claims,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function draftNarrativeOutreach(request) {
|
|
13
|
+
const { evidenceRefs, narrativeState, relationshipMemory, platformId } = request;
|
|
14
|
+
// T6.1.1 — Block when source refs are missing
|
|
15
|
+
if (!evidenceRefs || evidenceRefs.length === 0) {
|
|
16
|
+
return {
|
|
17
|
+
draft: {
|
|
18
|
+
whatHappened: "",
|
|
19
|
+
whyItMatters: "",
|
|
20
|
+
sourceRefs: [],
|
|
21
|
+
tone: "blocked",
|
|
22
|
+
},
|
|
23
|
+
groundingReport: {
|
|
24
|
+
status: "blocked",
|
|
25
|
+
sourceCoverage: 0,
|
|
26
|
+
unsupportedClaims: ["missing_evidence_refs"],
|
|
27
|
+
reason: "no_source_refs_provided",
|
|
28
|
+
},
|
|
29
|
+
promptVersion: "v6-rules-only-1.0",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Build claims from narrative state
|
|
33
|
+
const claims = [];
|
|
34
|
+
if (narrativeState?.focus) {
|
|
35
|
+
claims.push(narrativeState.focus);
|
|
36
|
+
}
|
|
37
|
+
if (narrativeState?.progress && narrativeState.progress.length > 0) {
|
|
38
|
+
claims.push(...narrativeState.progress);
|
|
39
|
+
}
|
|
40
|
+
const { coverage, unsupported } = computeSourceCoverage(claims, evidenceRefs, narrativeState?.sourceRefs);
|
|
41
|
+
// T6.1.1 — Degrade when source coverage is low
|
|
42
|
+
if (coverage < 0.5) {
|
|
43
|
+
return {
|
|
44
|
+
draft: {
|
|
45
|
+
whatHappened: narrativeState?.focus ?? "",
|
|
46
|
+
whyItMatters: "",
|
|
47
|
+
sourceRefs: evidenceRefs,
|
|
48
|
+
tone: "insufficient_history",
|
|
49
|
+
},
|
|
50
|
+
groundingReport: {
|
|
51
|
+
status: "degraded",
|
|
52
|
+
sourceCoverage: coverage,
|
|
53
|
+
unsupportedClaims: unsupported,
|
|
54
|
+
reason: "insufficient_source_coverage",
|
|
55
|
+
},
|
|
56
|
+
promptVersion: "v6-rules-only-1.0",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// T6.1.1 — insufficient_history tone when relationship is weak
|
|
60
|
+
const avgAffinity = relationshipMemory && relationshipMemory.topicAffinities.length > 0
|
|
61
|
+
? relationshipMemory.topicAffinities.reduce((s, t) => s + t.affinity, 0) /
|
|
62
|
+
relationshipMemory.topicAffinities.length
|
|
63
|
+
: 0;
|
|
64
|
+
const tone = avgAffinity < 0.3 ? "insufficient_history" : "friend";
|
|
65
|
+
const whatHappened = narrativeState?.focus ?? "Recent activity observed.";
|
|
66
|
+
const whyItMatters = narrativeState?.nextIntent
|
|
67
|
+
? `This relates to the current focus: ${narrativeState.nextIntent}.`
|
|
68
|
+
: "This may be relevant to your interests.";
|
|
69
|
+
return {
|
|
70
|
+
draft: {
|
|
71
|
+
whatHappened,
|
|
72
|
+
whyItMatters,
|
|
73
|
+
sourceRefs: evidenceRefs,
|
|
74
|
+
tone,
|
|
75
|
+
},
|
|
76
|
+
groundingReport: {
|
|
77
|
+
status: "grounded",
|
|
78
|
+
sourceCoverage: coverage,
|
|
79
|
+
unsupportedClaims: unsupported,
|
|
80
|
+
reason: "source_backed",
|
|
81
|
+
},
|
|
82
|
+
promptVersion: "v6-rules-only-1.0",
|
|
83
|
+
};
|
|
84
|
+
}
|