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

@ -4,7 +4,7 @@ import path from 'node:path';
const AGENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
export type AgentCommandName = 'agent register' | 'agent list' | 'agent show' | 'agent heartbeat';
export type AgentCommandName = 'agent register' | 'agent list' | 'agent show' | 'agent activity-lease';
export interface AgentCommandError {
code: string;
@ -24,7 +24,7 @@ export interface AgentRecord {
role: string;
status: string;
created_at: string;
last_seen_at: string;
last_seen_at: string; // Used as the base for the Activity Lease
version: number;
}
@ -48,7 +48,7 @@ export interface ShowAgentInput {
agent: string;
}
export interface HeartbeatAgentInput {
export interface ActivityLeaseInput {
agent: string;
}
@ -280,13 +280,14 @@ export function deriveLiveness(lastSeenAt: string, now: Date = new Date(), stale
}
/**
* Updates the last_seen_at timestamp for a registered agent.
* Extends the activity lease (last_seen_at timestamp) for a registered agent.
* Equivalent to a "parking permit" extension based on real work.
*/
export async function heartbeatAgent(
input: HeartbeatAgentInput,
export async function extendActivityLease(
input: ActivityLeaseInput,
deps: Partial<RegisterAgentDeps> = {},
): Promise<AgentCommandResponse<AgentRecord>> {
const command: AgentCommandName = 'agent heartbeat';
const command: AgentCommandName = 'agent activity-lease';
const agentId = trimOrEmpty(input.agent);
const agentIdError = validateAgentId(agentId);
@ -310,6 +311,6 @@ export async function heartbeatAgent(
await writeAgent(updated);
return success(command, updated);
} catch (error) {
return invalid(command, 'INTERNAL_ERROR', error instanceof Error ? error.message : 'Failed to heartbeat agent.');
return invalid(command, 'INTERNAL_ERROR', error instanceof Error ? error.message : 'Failed to extend activity lease.');
}
}