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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ Real-time push events. Per-agent streams deliver only events targeted at that ag

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/spaces/{space}/ignition/{agent}?tmux_session=` | Bootstrap agent with context + task |
| `GET` | `/spaces/{space}/ignition/{agent}?session_id=` | Bootstrap agent with context + task |

### Shared Data

Expand Down
2 changes: 1 addition & 1 deletion commands/boss.check.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ curl -s -X POST "http://localhost:8899/spaces/SPACE_URL_ENCODED/agent/AGENT_NAME

You MUST see `accepted for` in the response. If not, retry once.

**Note:** `repo_url` and `tmux_session` are sticky — the server remembers them after first send. You only need to include them on first check-in or if they change.
**Note:** `repo_url` and `session_id` are sticky — the server remembers them after first send. You only need to include them on first check-in or if they change.

## Step 3: Act on messages

Expand Down
20 changes: 10 additions & 10 deletions commands/boss.ignite.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ cat CLAUDE.md 2>/dev/null | head -60

Save your `branch` and `repo_url` values — include them in your first POST.

## Step 1: Get your tmux session name
## Step 1: Get your session name

```bash
tmux display-message -p '#S'
Expand All @@ -39,24 +39,24 @@ Save this value — you will need it in Step 2. Note: tmux sessions use bare nam

## Step 2: Fetch your ignition prompt

Using your agent name, the URL-encoded space name, and your tmux session:
Using your agent name, the URL-encoded space name, and your session name:

```bash
curl -s "http://localhost:8899/spaces/SPACE_URL_ENCODED/ignition/AGENT_NAME?tmux_session=YOUR_TMUX_SESSION"
curl -s "http://localhost:8899/spaces/SPACE_URL_ENCODED/ignition/AGENT_NAME?session_id=YOUR_SESSION"
```

For example, agent `ProtocolDev` in space `Agent Boss Development` with tmux session `ProtocolDev`:
For example, agent `ProtocolDev` in space `Agent Boss Development` with session `ProtocolDev`:

```bash
curl -s "http://localhost:8899/spaces/Agent%20Boss%20Development/ignition/ProtocolDev?tmux_session=ProtocolDev"
curl -s "http://localhost:8899/spaces/Agent%20Boss%20Development/ignition/ProtocolDev?session_id=ProtocolDev"
```

This registers your tmux session with the coordinator (**sticky** — no need to include in POST body) and returns your identity, peer agents, the full protocol, and a POST template.
This registers your session with the coordinator (**sticky** — no need to include in POST body) and returns your identity, peer agents, the full protocol, and a POST template.

**Optional hierarchy registration:** append `?parent=PARENT_NAME&role=ROLE` to pre-register your position in the agent hierarchy. `parent` sets your manager (sticky — ignored on subsequent calls if already set); `role` is a display label (e.g. `Developer`, `Manager`, `SME`). Example:
**Optional hierarchy registration:** append `&parent=PARENT_NAME&role=ROLE` to pre-register your position in the agent hierarchy. `parent` sets your manager (sticky — ignored on subsequent calls if already set); `role` is a display label (e.g. `Developer`, `Manager`, `SME`). Example:

```bash
curl -s "http://localhost:8899/spaces/SPACE_URL_ENCODED/ignition/AGENT_NAME?tmux_session=YOUR_TMUX_SESSION&parent=ManagerAgent&role=Developer"
curl -s "http://localhost:8899/spaces/SPACE_URL_ENCODED/ignition/AGENT_NAME?session_id=YOUR_SESSION&parent=ManagerAgent&role=Developer"
```

## Step 3: Read the blackboard
Expand Down Expand Up @@ -85,7 +85,7 @@ curl -s -X POST "http://localhost:8899/spaces/SPACE_URL_ENCODED/agent/AGENT_NAME
}'
```

`repo_url` and `tmux_session` are **sticky** — send once, server remembers them.
`repo_url` and `session_id` are **sticky** — send once, server remembers them.

## Work Loop

Expand All @@ -103,7 +103,7 @@ After ignition, operate autonomously — do NOT wait for human input:
- **Never contradict shared contracts** — agreed API surfaces and architectural decisions all agents must respect.
- **Tag questions with `[?BOSS]`** when you need the human to decide. Continue working on what you can while waiting.
- **Post to your own channel only** — the server rejects cross-channel posts (403).
- **Do NOT include `tmux_session` in your POST body** — it was pre-registered via `?tmux_session=` in Step 2 and is sticky.
- **Do NOT include `session_id` in your POST body** — it was pre-registered via `?session_id=` in Step 2 and is sticky.
- **Check for messages** — look for `#### Messages` under your name in `/raw`. Acknowledge and act in your next POST.
- **Always use `curl`** — never use the WebFetch tool; it does not work on localhost.
- **Send messages to other agents:**
Expand Down
20 changes: 10 additions & 10 deletions docs/AGENT_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ X-Agent-Name: {agent}
"blockers": ["waiting for DataMgr to merge PR #7"],
"parent": "ManagerAgent",
"role": "Developer",
"tmux_session": "MyAgent"
"session_id": "MyAgent"
}
```

Expand All @@ -162,9 +162,9 @@ X-Agent-Name: {agent}
| `blockers` | array | no | Highlighted in dashboard |
| `parent` | string | no | Manager agent name — sticky hierarchy link |
| `role` | string | no | Display label e.g. `"Developer"`, `"SME"` |
| `tmux_session` | string | no | Tmux session name (sticky — send once, server remembers) |
| `session_id` | string | no | Session name (sticky — send once, server remembers) |

**Sticky fields:** `branch`, `pr`, `repo_url`, `parent`, `role`, `tmux_session` are remembered by the server. Omitting them in a subsequent POST does not clear them.
**Sticky fields:** `branch`, `pr`, `repo_url`, `parent`, `role`, `session_id` are remembered by the server. Omitting them in a subsequent POST does not clear them.

**Response:** `202 Accepted` on success.

Expand Down Expand Up @@ -448,14 +448,14 @@ Acknowledged messages are marked `"read": true` and their `read_at` timestamp is
**Bootstrap your agent identity, retrieve peer state, and get your protocol template.**

```
GET /spaces/{space}/ignition/{agent}?tmux_session={session}&parent={parent}&role={role}
GET /spaces/{space}/ignition/{agent}?session_id={session}&parent={parent}&role={role}
```

**Query parameters:**

| Parameter | Notes |
|-----------|-------|
| `tmux_session` | Tmux session name — registers sticky session mapping. Omit for non-tmux agents. |
| `session_id` | Session name — registers sticky session mapping. Omit for non-session agents. |
| `parent` | Manager agent name — sticky hierarchy link. |
| `role` | Display label e.g. `"Developer"`, `"SME"`. |

Expand Down Expand Up @@ -483,7 +483,7 @@ GET /spaces/{space}/agent/{agent}/introspect
```json
{
"agent": "MyAgent",
"tmux_session": "MyAgent",
"session_id": "MyAgent",
"pane_text": "...<last 50 lines of terminal output>...",
"captured_at": "2026-03-07T01:13:30Z"
}
Expand All @@ -494,13 +494,13 @@ GET /spaces/{space}/agent/{agent}/introspect
```json
{
"agent": "MyAgent",
"tmux_session": "",
"session_id": "",
"pane_text": "",
"captured_at": "2026-03-07T01:13:30Z"
}
```

Non-tmux agents return empty `pane_text` and `tmux_session` rather than an error. The `captured_at` timestamp reflects when the introspect was attempted.
Non-tmux agents return empty `pane_text` and `session_id` rather than an error. The `captured_at` timestamp reflects when the introspect was attempted.

---

Expand Down Expand Up @@ -654,7 +654,7 @@ Sent every 15 seconds to prevent proxy timeouts. Not a named event — SSE comme

**Events NOT delivered on the per-agent stream:**
- Other agents' status updates
- `tmux_liveness` signals from other agents
- `session_liveness` signals from other agents
- Space-wide broadcast noise

The per-agent stream delivers only events relevant to your agent — this is the key scalability advantage over polling `/raw`.
Expand All @@ -681,7 +681,7 @@ Non-tmux agents (Docker, CI, remote, script) interact with the coordinator exact

| Feature | Tmux Agent | Non-Tmux Agent |
|---------|-----------|----------------|
| Session registration | `?tmux_session=Name` on ignition | Omit; or set `"agent_type": "http"` in registration |
| Session registration | `?session_id=Name` on ignition | Omit; or set `"agent_type": "http"` in registration |
| Introspect | Returns live terminal output | Returns empty `pane_text` (no error) |
| Spawn/stop/restart | Server sends tmux commands | No-op (returns `202 Accepted` but takes no action) |
| Staleness detection | Auto-detected via tmux liveness | Manual via heartbeat if `heartbeat_interval_sec > 0` |
Expand Down
6 changes: 3 additions & 3 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ data: {"space":"...","agent":"...","sender":"...","message":"..."}
| `agent_restarted` | Agent tmux session restarted |
| `space_deleted` | Space deleted |
| `broadcast_complete` | Check-in broadcast finished |
| `tmux_liveness` | Tmux session liveness probe (every second) |
| `session_liveness` | Session liveness probe (every second) |

---

Expand Down Expand Up @@ -503,7 +503,7 @@ Ignition bootstraps an agent with its identity, peers, and task context.
### Get Ignition Prompt

```
GET /spaces/{space}/ignition/{agent}?tmux_session={session}
GET /spaces/{space}/ignition/{agent}?session_id={session}
```

Returns a structured ignition document containing:
Expand All @@ -513,7 +513,7 @@ Returns a structured ignition document containing:
- Pending messages
- JSON POST template

The `tmux_session` query parameter registers the agent's tmux session (sticky — remembered for future status updates).
The `session_id` query parameter registers the agent's session (sticky — remembered for future status updates).

---

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ curl -s -N http://localhost:8899/spaces/my-project/agent/Developer/events \
The ignition endpoint bootstraps a new agent with full context — its identity, peer status, pending messages, and a POST template:

```bash
curl -s "http://localhost:8899/spaces/my-project/ignition/Developer?tmux_session=dev-session"
curl -s "http://localhost:8899/spaces/my-project/ignition/Developer?session_id=dev-session"
```

This is how the `/boss.ignite` skill works in Claude Code sessions.
Expand Down
4 changes: 2 additions & 2 deletions docs/hierarchy-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ This is O(N) where N = number of agents per space — acceptable for team-scale

### Sticky `Parent` semantics

`Parent` is **mutable and sticky** — identical to `TmuxSession`. A status POST that includes `parent` updates it. A status POST that omits `parent` does NOT clear it. To remove a parent, an agent must POST `"parent": ""` explicitly.
`Parent` is **mutable and sticky** — identical to `SessionID`. A status POST that includes `parent` updates it. A status POST that omits `parent` does NOT clear it. To remove a parent, an agent must POST `"parent": ""` explicitly.

### Cycle Detection — must be atomic with write

Expand Down Expand Up @@ -417,7 +417,7 @@ Required test functions (10 minimum, all must pass `-race`):

## 12. Decisions (CTO, 2026-03-07)

1. **Parent mutability**: Parent IS mutable — sticky pattern same as `TmuxSession`. Agents can re-declare parent on any status POST; omitting `parent` does not clear it.
1. **Parent mutability**: Parent IS mutable — sticky pattern same as `SessionID`. Agents can re-declare parent on any status POST; omitting `parent` does not clear it.
2. **Scope**: Hierarchy is **per-space**. No global hierarchy across spaces.
3. **Fan-out delivery**: `scope=subtree` is **async** (fire-and-forget per recipient). Server returns 202 immediately. Each recipient's SSE stream fires independently via goroutine.
4. **Dashboard**: Hierarchy tab exists **alongside** the flat Session Dashboard — it does not replace it.
Expand Down
8 changes: 4 additions & 4 deletions docs/lifecycle-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Creates a tmux session, launches the agent command, and sends the `/boss.ignite`
**Request body (optional JSON):**
```json
{
"tmux_session": "MySession", // defaults to agent name
"session_id": "MySession", // defaults to agent name
"command": "claude --dangerously-skip-permissions", // default
"width": 220, // default
"height": 50 // default
Expand All @@ -73,7 +73,7 @@ Creates a tmux session, launches the agent command, and sends the `/boss.ignite`
{
"ok": true,
"agent": "AgentName",
"tmux_session": "MySession",
"session_id": "MySession",
"space": "SpaceName"
}
```
Expand Down Expand Up @@ -134,7 +134,7 @@ Kills the existing session (if any) and spawns a new one. Agent data is preserve
{
"ok": true,
"agent": "canonical-name",
"tmux_session": "new-session-name"
"session_id": "new-session-name"
}
```

Expand Down Expand Up @@ -162,7 +162,7 @@ Captures the agent's current tmux pane output and returns it with inferred state
```json
{
"agent": "canonical-name",
"tmux_session": "session-name",
"session_id": "session-name",
"session_exists": true,
"idle": false,
"needs_approval": true,
Expand Down
2 changes: 1 addition & 1 deletion docs/proposal-agent-boss-ambient.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ graph TB
end

TM -->|detect approvals| IL
TM -->|SSE tmux_liveness| SSE
TM -->|SSE session_liveness| SSE
SSE --> OV
SSE --> IT
BL -->|saveSpace| PR
Expand Down
6 changes: 3 additions & 3 deletions docs/sse-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Events currently broadcast (all with raw JSON data):
- `agent_updated` — any POST to agent channel
- `agent_removed`
- `agent_message` — when a message is sent to an agent
- `tmux_liveness` — every second per agent
- `session_liveness` — every second per agent
- `broadcast_complete`
- `space_deleted`
- `agent_spawned` / `agent_stopped` / `agent_restarted` (lifecycle)
Expand Down Expand Up @@ -105,7 +105,7 @@ An agent subscribing to `GET /spaces/{space}/agent/{name}/events` should receive

Events NOT included (to prevent context pollution):
- Other agents' `agent_updated` events
- `tmux_liveness` for other agents
- `session_liveness` for other agents
- Space-wide broadcast noise

### 3.3 Agent Filtering Fix
Expand Down Expand Up @@ -296,7 +296,7 @@ data: {...}
| No `Last-Event-ID` replay | Medium | Add per-agent event ring buffer |
| No `X-Accel-Buffering: no` header | Medium | Add to SSE response headers |
| Webhook fires even when SSE active | Low | Check `hasActiveSSE` before webhook |
| `tmux_liveness` events in stream | Low | Exclude from per-agent stream |
| `session_liveness` events in stream | Low | Exclude from per-agent stream |

---

Expand Down
28 changes: 14 additions & 14 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { SpaceSummary, KnowledgeSpace, TmuxAgentStatus, AgentUpdate, HierarchyTree, HierarchyNode } from '@/types'
import type { SpaceSummary, KnowledgeSpace, SessionAgentStatus, AgentUpdate, HierarchyTree, HierarchyNode } from '@/types'
import { api } from '@/api/client'
import { useSSE } from '@/composables/useSSE'

Expand Down Expand Up @@ -37,7 +37,7 @@ const router = useRouter()
// ── State ──────────────────────────────────────────────────────────
const spaces = ref<SpaceSummary[]>([])
const currentSpace = ref<KnowledgeSpace | null>(null)
const tmuxStatus = ref<Record<string, TmuxAgentStatus>>({})
const tmuxStatus = ref<Record<string, SessionAgentStatus>>({})
const hierarchyTree = ref<HierarchyTree | null>(null)

const loading = ref(true)
Expand Down Expand Up @@ -96,7 +96,7 @@ const selectedAgentData = computed<AgentUpdate | null>(() => {
return currentSpace.value.agents[selectedAgent.value] ?? null
})

const selectedAgentTmux = computed<TmuxAgentStatus | null>(() => {
const selectedAgentTmux = computed<SessionAgentStatus | null>(() => {
if (!selectedAgent.value) return null
return tmuxStatus.value[selectedAgent.value] ?? null
})
Expand Down Expand Up @@ -209,12 +209,12 @@ async function loadHierarchy(space: string) {
}
}

async function loadTmuxStatus(space: string) {
async function loadSessionStatus(space: string) {
try {
const raw = await api.fetchTmuxStatus(space)
const raw = await api.fetchSessionStatus(space)
// The server returns an array of {agent, ...} objects — normalize to a map
if (Array.isArray(raw)) {
const map: Record<string, TmuxAgentStatus> = {}
const map: Record<string, SessionAgentStatus> = {}
for (const item of raw as any[]) {
if (item.agent) {
map[item.agent] = item
Expand Down Expand Up @@ -246,7 +246,7 @@ watch(
currentSpace.value = null // clear stale data immediately
hierarchyTree.value = null
loadSpace(space, true)
loadTmuxStatus(space)
loadSessionStatus(space)
loadHierarchy(space)
// Reconnect SSE to this space
sse.disconnect()
Expand Down Expand Up @@ -280,7 +280,7 @@ async function handleApproveAgent() {
if (!selectedSpace.value || !selectedAgent.value) return
try {
await api.approveAgent(selectedSpace.value, selectedAgent.value)
await loadTmuxStatus(selectedSpace.value)
await loadSessionStatus(selectedSpace.value)
showStatus(`Approved ${selectedAgent.value}`)
} catch (err) {
console.error('Approve failed:', err)
Expand Down Expand Up @@ -562,17 +562,17 @@ function setupSSE() {
}
})

sse.on('tmux_liveness', (data) => {
sse.on('session_liveness', (data) => {
if (Array.isArray(data)) {
const map: Record<string, TmuxAgentStatus> = { ...tmuxStatus.value }
const map: Record<string, SessionAgentStatus> = { ...tmuxStatus.value }
for (const item of data) {
if (item.agent) {
map[item.agent] = item as TmuxAgentStatus
map[item.agent] = item as SessionAgentStatus
}
}
tmuxStatus.value = map
}
// tmux_liveness is high-frequency, don't spam the log
// session_liveness is high-frequency, don't spam the log
})

sse.on('agent_message', (data) => {
Expand Down Expand Up @@ -604,7 +604,7 @@ function startPolling() {
if (document.hidden) return
if (selectedSpace.value) {
loadSpace(selectedSpace.value)
loadTmuxStatus(selectedSpace.value)
loadSessionStatus(selectedSpace.value)
}
}, POLL_INTERVAL_MS)
}
Expand Down Expand Up @@ -758,7 +758,7 @@ onMounted(async () => {
if (selectedSpace.value) {
// Route already has a space — load its data and connect SSE
loadSpace(selectedSpace.value, true)
loadTmuxStatus(selectedSpace.value)
loadSessionStatus(selectedSpace.value)
sse.connect(selectedSpace.value)
} else if (spaces.value.length > 0) {
// No space in URL — redirect to first space
Expand Down
Loading