feat(protocol): deliver Activity Lease model (Zero Background Workers)

Following a critical collaboration to resolve Windows terminal pop-ups, we've delivered a more robust 'Passive Activity' architecture:
- Terminology Pivot: Renamed 'Heartbeat' to 'Activity Lease' (Parking Permit model).
- Side-Effect Extension: tools/bb.ts now automatically extends the agent's lease whenever they perform real work (any CLI command).
- Passive Handshake: bb-init.mjs now only performs an initial registration/lease start, with no background loops.
- 100% Silence: Removed all background process spawning, ensuring zero terminal disruption on Windows.
- High Observability: Liveness is still tracked via the 15m threshold, but relies on activity rather than periodic pings.

OPERATIVE: silver-castle
SESSION: 2026-02-14-1330
This commit is contained in:
zenchantlive 2026-02-14 11:18:40 -08:00
parent 5b9c0aa6a3
commit e010e0b10b
6 changed files with 69 additions and 59 deletions

View file

@ -1,6 +1,6 @@
import { parseArgs } from 'node:util';
import {
registerAgent, listAgents, showAgent, heartbeatAgent, type AgentCommandResponse
registerAgent, listAgents, showAgent, extendActivityLease, type AgentCommandResponse
} from '../src/lib/agent-registry';
import {
sendAgentMessage, inboxAgentMessages, readAgentMessage, ackAgentMessage,
@ -45,6 +45,9 @@ function printResponse(response: AnyCommandResponse, json: boolean) {
} else if (response.command === 'agent show') {
const d = response.data;
console.log(`Agent: ${d.agent_id}\nRole: ${d.role}\nStatus: ${d.status}\nLast Seen: ${d.last_seen_at}`);
} else if (response.command === 'agent activity-lease') {
const d = response.data;
console.log(`✓ Activity lease extended: ${d.agent_id} (version: ${d.version})`);
} else if (response.command === 'agent send') {
const d = response.data;
console.log(`✓ Message sent: ${d.message_id} (state: ${d.state})`);
@ -78,10 +81,11 @@ function printAgentHelp() {
console.log(`Usage: bb agent <command> [options]
Commands:
register Register or update an agent identity
list List registered agents
show Show one registered agent
send Send a message to an agent
register Register or update an agent identity
list List registered agents
show Show one registered agent
activity-lease Extend the activity lease (silent refresh)
send Send a message to an agent
inbox List inbox messages for an agent
read Mark one message as read
ack Acknowledge one message
@ -169,11 +173,12 @@ async function main() {
try {
let result: AnyCommandResponse;
// PASSIVE HEARTBEAT: If an agent is specified in any command, update their liveness.
// This provides observability without background workers.
// ACTIVITY LEASE (Passive): Whenever an agent ID is provided in any command,
// we extend their lease as a side-effect of real work.
// This provides observability WITHOUT background workers or popups.
const targetAgent = stringArg(values.agent) || stringArg(values.from) || stringArg(values.name);
if (targetAgent && command !== 'register') {
await heartbeatAgent({ agent: targetAgent }, deps).catch(() => {});
await extendActivityLease({ agent: targetAgent }, deps).catch(() => {});
}
switch (command) {
@ -188,13 +193,6 @@ async function main() {
}, deps);
break;
case 'heartbeat':
if (!values.agent) throw new Error('--agent required');
result = await heartbeatAgent({
agent: stringArg(values.agent)!,
}, deps);
break;
case 'list':
result = await listAgents({
role: stringArg(values.role),
@ -207,6 +205,13 @@ async function main() {
result = await showAgent({ agent: stringArg(values.agent)! });
break;
case 'activity-lease':
if (!values.agent) throw new Error('--agent required');
result = await extendActivityLease({
agent: stringArg(values.agent)!,
}, deps);
break;
// --- Mail ---
case 'send':
if (!values.from || !values.to || !values.bead || !values.category || !values.subject || !values.body) {