fix: realtime context display
This commit is contained in:
@@ -808,6 +808,7 @@ class EventLoopNode(NodeProtocol):
|
||||
execution_id,
|
||||
extra_data=_iter_meta,
|
||||
)
|
||||
await self._publish_context_usage(ctx, conversation, "iteration_start")
|
||||
|
||||
# 6d. Pre-turn compaction check (tiered)
|
||||
_compacted_this_iter = False
|
||||
@@ -2837,6 +2838,8 @@ class EventLoopNode(NodeProtocol):
|
||||
conversation.usage_ratio() * 100,
|
||||
)
|
||||
|
||||
await self._publish_context_usage(ctx, conversation, "post_tool_results")
|
||||
|
||||
# If the turn requested external input (ask_user or queen handoff),
|
||||
# return immediately so the outer loop can block before judge eval.
|
||||
if user_input_requested or queen_input_requested:
|
||||
@@ -4007,15 +4010,13 @@ class EventLoopNode(NodeProtocol):
|
||||
does not fully resolve the budget.
|
||||
4. Emergency deterministic summary only if LLM failed or unavailable.
|
||||
"""
|
||||
import os as _os
|
||||
|
||||
ratio_before = conversation.usage_ratio()
|
||||
phase_grad = getattr(ctx, "continuous_mode", False)
|
||||
|
||||
# Capture pre-compaction message inventory when over budget and debug
|
||||
# is enabled, since compaction mutates the conversation in place.
|
||||
# Capture pre-compaction message inventory when over budget,
|
||||
# since compaction mutates the conversation in place.
|
||||
pre_inventory: list[dict[str, Any]] | None = None
|
||||
if ratio_before >= 1.0 and _os.environ.get("HIVE_COMPACTION_DEBUG"):
|
||||
if ratio_before >= 1.0:
|
||||
pre_inventory = self._build_message_inventory(conversation)
|
||||
|
||||
# --- Step 1: Prune old tool results (free, no LLM) ---
|
||||
@@ -4340,19 +4341,25 @@ class EventLoopNode(NodeProtocol):
|
||||
if self._event_bus:
|
||||
from framework.runtime.event_bus import AgentEvent, EventType
|
||||
|
||||
event_data: dict[str, Any] = {
|
||||
"level": level,
|
||||
"usage_before": before_pct,
|
||||
"usage_after": after_pct,
|
||||
}
|
||||
if pre_inventory is not None:
|
||||
event_data["message_inventory"] = pre_inventory
|
||||
await self._event_bus.publish(
|
||||
AgentEvent(
|
||||
type=EventType.CONTEXT_COMPACTED,
|
||||
stream_id=ctx.stream_id or ctx.node_id,
|
||||
node_id=ctx.node_id,
|
||||
data={
|
||||
"level": level,
|
||||
"usage_before": before_pct,
|
||||
"usage_after": after_pct,
|
||||
},
|
||||
data=event_data,
|
||||
)
|
||||
)
|
||||
|
||||
# Emit post-compaction usage update
|
||||
await self._publish_context_usage(ctx, conversation, "post_compaction")
|
||||
|
||||
# Write detailed debug log to ~/.hive/compaction_log/ when enabled
|
||||
if _os.environ.get("HIVE_COMPACTION_DEBUG"):
|
||||
self._write_compaction_debug_log(
|
||||
@@ -4842,6 +4849,36 @@ class EventLoopNode(NodeProtocol):
|
||||
if result.inject:
|
||||
await conversation.add_user_message(result.inject)
|
||||
|
||||
async def _publish_context_usage(
|
||||
self,
|
||||
ctx: NodeContext,
|
||||
conversation: NodeConversation,
|
||||
trigger: str,
|
||||
) -> None:
|
||||
"""Emit a CONTEXT_USAGE_UPDATED event with current context window state."""
|
||||
if not self._event_bus:
|
||||
return
|
||||
from framework.runtime.event_bus import AgentEvent, EventType
|
||||
|
||||
estimated = conversation.estimate_tokens()
|
||||
max_tokens = conversation._max_context_tokens
|
||||
ratio = estimated / max_tokens if max_tokens > 0 else 0.0
|
||||
await self._event_bus.publish(
|
||||
AgentEvent(
|
||||
type=EventType.CONTEXT_USAGE_UPDATED,
|
||||
stream_id=ctx.stream_id or ctx.node_id,
|
||||
node_id=ctx.node_id,
|
||||
data={
|
||||
"usage_ratio": round(ratio, 4),
|
||||
"usage_pct": round(ratio * 100),
|
||||
"message_count": conversation.message_count,
|
||||
"estimated_tokens": estimated,
|
||||
"max_context_tokens": max_tokens,
|
||||
"trigger": trigger,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
async def _publish_iteration(
|
||||
self,
|
||||
stream_id: str,
|
||||
|
||||
@@ -117,6 +117,7 @@ class EventType(StrEnum):
|
||||
|
||||
# Context management
|
||||
CONTEXT_COMPACTED = "context_compacted"
|
||||
CONTEXT_USAGE_UPDATED = "context_usage_updated"
|
||||
|
||||
# External triggers
|
||||
WEBHOOK_RECEIVED = "webhook_received"
|
||||
|
||||
@@ -37,6 +37,7 @@ DEFAULT_EVENT_TYPES = [
|
||||
EventType.NODE_RETRY,
|
||||
EventType.NODE_TOOL_DOOM_LOOP,
|
||||
EventType.CONTEXT_COMPACTED,
|
||||
EventType.CONTEXT_USAGE_UPDATED,
|
||||
EventType.WORKER_LOADED,
|
||||
EventType.CREDENTIALS_REQUIRED,
|
||||
EventType.SUBAGENT_REPORT,
|
||||
|
||||
@@ -324,6 +324,7 @@ export type EventTypeName =
|
||||
| "node_retry"
|
||||
| "edge_traversed"
|
||||
| "context_compacted"
|
||||
| "context_usage_updated"
|
||||
| "webhook_received"
|
||||
| "custom"
|
||||
| "escalation_requested"
|
||||
|
||||
@@ -28,6 +28,13 @@ export interface SubagentReport {
|
||||
status?: "running" | "complete" | "error";
|
||||
}
|
||||
|
||||
interface ContextUsage {
|
||||
usagePct: number;
|
||||
messageCount: number;
|
||||
estimatedTokens: number;
|
||||
maxTokens: number;
|
||||
}
|
||||
|
||||
interface NodeDetailPanelProps {
|
||||
node: GraphNode | null;
|
||||
nodeSpec?: NodeSpec | null;
|
||||
@@ -38,6 +45,7 @@ interface NodeDetailPanelProps {
|
||||
workerSessionId?: string | null;
|
||||
nodeLogs?: string[];
|
||||
actionPlan?: string;
|
||||
contextUsage?: ContextUsage;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -309,7 +317,7 @@ const tabs: { id: Tab; label: string; Icon: React.FC<{ className?: string }> }[]
|
||||
{ id: "subagents", label: "Subagents", Icon: ({ className }) => <Bot className={className} /> },
|
||||
];
|
||||
|
||||
export default function NodeDetailPanel({ node, nodeSpec, allNodeSpecs, subagentReports, sessionId, graphId, workerSessionId, nodeLogs, actionPlan, onClose }: NodeDetailPanelProps) {
|
||||
export default function NodeDetailPanel({ node, nodeSpec, allNodeSpecs, subagentReports, sessionId, graphId, workerSessionId, nodeLogs, actionPlan, contextUsage, onClose }: NodeDetailPanelProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>("overview");
|
||||
const [realTools, setRealTools] = useState<ToolInfo[] | null>(null);
|
||||
const [realCriteria, setRealCriteria] = useState<NodeCriteria | null>(null);
|
||||
@@ -389,6 +397,43 @@ export default function NodeDetailPanel({ node, nodeSpec, allNodeSpecs, subagent
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Context window usage */}
|
||||
{contextUsage && (
|
||||
<div className="px-4 py-2 border-b border-border/20 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-[10px] text-muted-foreground font-medium">Context</span>
|
||||
<span className="text-[10px] text-muted-foreground/70 ml-auto">
|
||||
{(contextUsage.estimatedTokens / 1000).toFixed(1)}k / {(contextUsage.maxTokens / 1000).toFixed(0)}k tokens
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 rounded-full bg-muted/50 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-500 ease-out"
|
||||
style={{
|
||||
width: `${Math.min(contextUsage.usagePct, 100)}%`,
|
||||
backgroundColor: contextUsage.usagePct >= 90
|
||||
? "hsl(0,65%,55%)"
|
||||
: contextUsage.usagePct >= 70
|
||||
? "hsl(35,90%,55%)"
|
||||
: "hsl(45,95%,58%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-[10px] text-muted-foreground/60">{contextUsage.messageCount} messages</span>
|
||||
<span className="text-[10px] font-medium ml-auto" style={{
|
||||
color: contextUsage.usagePct >= 90
|
||||
? "hsl(0,65%,55%)"
|
||||
: contextUsage.usagePct >= 70
|
||||
? "hsl(35,90%,55%)"
|
||||
: "hsl(45,95%,58%)",
|
||||
}}>
|
||||
{contextUsage.usagePct}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex border-b border-border/30 flex-shrink-0 px-2 pt-1 overflow-x-auto scrollbar-hide">
|
||||
{tabs.filter(t => t.id !== "subagents" || (nodeSpec?.sub_agents && nodeSpec.sub_agents.length > 0)).map(tab => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import { Plus, KeyRound, Sparkles, Layers, ChevronLeft, Bot, Loader2, WifiOff, X } from "lucide-react";
|
||||
import { Plus, KeyRound, Sparkles, Layers, ChevronLeft, Bot, Loader2, WifiOff, X, Crown, Cpu } from "lucide-react";
|
||||
import type { GraphNode, NodeStatus } from "@/components/graph-types";
|
||||
import DraftGraph from "@/components/DraftGraph";
|
||||
import ChatPanel, { type ChatMessage } from "@/components/ChatPanel";
|
||||
@@ -352,6 +352,8 @@ interface AgentBackendState {
|
||||
pendingQuestions: { id: string; prompt: string; options?: string[] }[] | null;
|
||||
/** Whether the pending question came from queen or worker */
|
||||
pendingQuestionSource: "queen" | "worker" | null;
|
||||
/** Per-node context window usage (from context_usage_updated events) */
|
||||
contextUsage: Record<string, { usagePct: number; messageCount: number; estimatedTokens: number; maxTokens: number }>;
|
||||
}
|
||||
|
||||
function defaultAgentState(): AgentBackendState {
|
||||
@@ -389,6 +391,7 @@ function defaultAgentState(): AgentBackendState {
|
||||
pendingOptions: null,
|
||||
pendingQuestions: null,
|
||||
pendingQuestionSource: null,
|
||||
contextUsage: {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2155,6 +2158,29 @@ export default function Workspace() {
|
||||
}
|
||||
break;
|
||||
|
||||
case "context_usage_updated": {
|
||||
const streamKey = isQueen ? "__queen__" : (event.node_id || streamId);
|
||||
const usagePct = (event.data?.usage_pct as number) ?? 0;
|
||||
const messageCount = (event.data?.message_count as number) ?? 0;
|
||||
const estimatedTokens = (event.data?.estimated_tokens as number) ?? 0;
|
||||
const maxTokens = (event.data?.max_context_tokens as number) ?? 0;
|
||||
setAgentStates(prev => {
|
||||
const state = prev[agentType];
|
||||
if (!state) return prev;
|
||||
return {
|
||||
...prev,
|
||||
[agentType]: {
|
||||
...state,
|
||||
contextUsage: {
|
||||
...state.contextUsage,
|
||||
[streamKey]: { usagePct, messageCount, estimatedTokens, maxTokens },
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "node_action_plan":
|
||||
if (!isQueen && event.node_id) {
|
||||
const plan = (event.data?.plan as string) || "";
|
||||
@@ -3169,6 +3195,58 @@ export default function Workspace() {
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Context window usage bars — floating overlay at bottom of chat */}
|
||||
{(() => {
|
||||
const cu = activeAgentState?.contextUsage;
|
||||
if (!cu) return null;
|
||||
const queenUsage = cu["__queen__"];
|
||||
const workerEntries = Object.entries(cu).filter(([k]) => k !== "__queen__");
|
||||
const workerUsage = workerEntries.length > 0
|
||||
? workerEntries.reduce((best, [, v]) => (v.usagePct > best.usagePct ? v : best), workerEntries[0][1])
|
||||
: undefined;
|
||||
if (!queenUsage && !workerUsage) return null;
|
||||
return (
|
||||
<div className="absolute bottom-16 left-3 right-3 z-20 flex items-center gap-3 px-3 py-1.5 rounded-lg bg-background/80 backdrop-blur-sm border border-border/30 shadow-sm group/ctx pointer-events-auto">
|
||||
{queenUsage && (
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 relative" title={`Queen: ${(queenUsage.estimatedTokens / 1000).toFixed(1)}k / ${(queenUsage.maxTokens / 1000).toFixed(0)}k tokens \u00b7 ${queenUsage.messageCount} messages`}>
|
||||
<Crown className="w-3 h-3 flex-shrink-0" style={{ color: "hsl(45,95%,58%)" }} />
|
||||
<div className="flex-1 h-1.5 rounded-full bg-muted/50 overflow-hidden min-w-[60px]">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-500 ease-out"
|
||||
style={{
|
||||
width: `${Math.min(queenUsage.usagePct, 100)}%`,
|
||||
backgroundColor: queenUsage.usagePct >= 90 ? "hsl(0,65%,55%)" : queenUsage.usagePct >= 70 ? "hsl(35,90%,55%)" : "hsl(45,95%,58%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground/70 flex-shrink-0 tabular-nums">
|
||||
<span className="group-hover/ctx:hidden">{queenUsage.usagePct}%</span>
|
||||
<span className="hidden group-hover/ctx:inline">{(queenUsage.estimatedTokens / 1000).toFixed(1)}k / {(queenUsage.maxTokens / 1000).toFixed(0)}k</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{workerUsage && (
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 relative" title={`Worker: ${(workerUsage.estimatedTokens / 1000).toFixed(1)}k / ${(workerUsage.maxTokens / 1000).toFixed(0)}k tokens \u00b7 ${workerUsage.messageCount} messages`}>
|
||||
<Cpu className="w-3 h-3 flex-shrink-0" style={{ color: "hsl(220,60%,55%)" }} />
|
||||
<div className="flex-1 h-1.5 rounded-full bg-muted/50 overflow-hidden min-w-[60px]">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-500 ease-out"
|
||||
style={{
|
||||
width: `${Math.min(workerUsage.usagePct, 100)}%`,
|
||||
backgroundColor: workerUsage.usagePct >= 90 ? "hsl(0,65%,55%)" : workerUsage.usagePct >= 70 ? "hsl(35,90%,55%)" : "hsl(220,60%,55%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground/70 flex-shrink-0 tabular-nums">
|
||||
<span className="group-hover/ctx:hidden">{workerUsage.usagePct}%</span>
|
||||
<span className="hidden group-hover/ctx:inline">{(workerUsage.estimatedTokens / 1000).toFixed(1)}k / {(workerUsage.maxTokens / 1000).toFixed(0)}k</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{activeSession && (
|
||||
<ChatPanel
|
||||
messages={activeSession.messages}
|
||||
@@ -3396,6 +3474,7 @@ export default function Workspace() {
|
||||
workerSessionId={null}
|
||||
nodeLogs={activeAgentState?.nodeLogs[resolvedSelectedNode.id] || []}
|
||||
actionPlan={activeAgentState?.nodeActionPlans[resolvedSelectedNode.id]}
|
||||
contextUsage={activeAgentState?.contextUsage[resolvedSelectedNode.id]}
|
||||
onClose={() => setSelectedNode(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# Agent Skills User Guide
|
||||
|
||||
This guide covers how to use, create, and manage Agent Skills in the Hive framework. Agent Skills follow the open [Agent Skills standard](https://agentskills.io) — skills written for Claude Code, Cursor, or other compatible agents work in Hive unchanged.
|
||||
|
||||
## What are skills?
|
||||
|
||||
Skills are folders containing a `SKILL.md` file that teaches an agent how to perform a specific task. They can also bundle scripts, templates, and reference materials. Skills are loaded on demand — the agent sees a lightweight catalog at startup and pulls in full instructions only when relevant.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Install a skill
|
||||
|
||||
Drop a skill folder into one of the discovery directories:
|
||||
|
||||
```bash
|
||||
# Project-level (shared with the repo)
|
||||
mkdir -p .hive/skills/my-skill
|
||||
cat > .hive/skills/my-skill/SKILL.md << 'EOF'
|
||||
---
|
||||
name: my-skill
|
||||
description: Does X when the user asks about Y.
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
Step-by-step instructions for the agent...
|
||||
EOF
|
||||
```
|
||||
|
||||
The agent will discover it automatically on the next session.
|
||||
|
||||
### List discovered skills
|
||||
|
||||
```bash
|
||||
hive skill list
|
||||
```
|
||||
|
||||
Output groups skills by scope:
|
||||
|
||||
```
|
||||
PROJECT SKILLS
|
||||
────────────────────────────────────
|
||||
• my-skill
|
||||
Does X when the user asks about Y.
|
||||
/home/user/project/.hive/skills/my-skill/SKILL.md
|
||||
|
||||
USER SKILLS
|
||||
────────────────────────────────────
|
||||
• deep-research
|
||||
Multi-step web research with source verification.
|
||||
/home/user/.hive/skills/deep-research/SKILL.md
|
||||
```
|
||||
|
||||
## Where to put skills
|
||||
|
||||
Hive scans five directories at startup, in this precedence order:
|
||||
|
||||
| Scope | Path | Use case |
|
||||
|-------|------|----------|
|
||||
| Project (Hive) | `<project>/.hive/skills/` | Skills specific to this repo |
|
||||
| Project (cross-client) | `<project>/.agents/skills/` | Skills shared across Claude Code, Cursor, etc. |
|
||||
| User (Hive) | `~/.hive/skills/` | Personal skills available in all projects |
|
||||
| User (cross-client) | `~/.agents/skills/` | Personal cross-client skills |
|
||||
| Framework | *(built-in)* | Default operational skills shipped with Hive |
|
||||
|
||||
**Precedence**: If two skills share the same name, the higher-precedence location wins. A project-level `code-review` skill overrides a user-level one with the same name.
|
||||
|
||||
**Cross-client paths**: The `.agents/skills/` directories are a convention shared across compatible agents. A skill installed at `~/.agents/skills/pdf-processing/` is visible to Hive, Claude Code, Cursor, and other compatible tools simultaneously.
|
||||
|
||||
## Creating a skill
|
||||
|
||||
### Directory structure
|
||||
|
||||
```
|
||||
my-skill/
|
||||
├── SKILL.md # Required — metadata + instructions
|
||||
├── scripts/ # Optional — executable code
|
||||
│ └── run.py
|
||||
├── references/ # Optional — supplementary docs
|
||||
│ └── api-reference.md
|
||||
└── assets/ # Optional — templates, data files
|
||||
└── template.json
|
||||
```
|
||||
|
||||
### SKILL.md format
|
||||
|
||||
Every skill needs a `SKILL.md` with YAML frontmatter and a markdown body:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Extract and summarize PDF documents. Use when the user mentions PDFs or document extraction.
|
||||
---
|
||||
|
||||
# PDF Processing
|
||||
|
||||
## When to use
|
||||
Use this skill when the user needs to extract text from PDFs or merge documents.
|
||||
|
||||
## Steps
|
||||
1. Check if pdfplumber is available...
|
||||
2. Extract text using...
|
||||
|
||||
## Edge cases
|
||||
- Scanned PDFs need OCR first...
|
||||
```
|
||||
|
||||
### Frontmatter fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `name` | Yes | Lowercase letters, numbers, hyphens. Must match the parent directory name. Max 64 chars. |
|
||||
| `description` | Yes | What the skill does and when to use it. Max 1024 chars. Include keywords that help the agent match tasks. |
|
||||
| `license` | No | License name or reference to a bundled LICENSE file. |
|
||||
| `compatibility` | No | Environment requirements (e.g., "Requires git, docker"). |
|
||||
| `metadata` | No | Arbitrary key-value pairs (author, version, etc.). |
|
||||
| `allowed-tools` | No | Space-delimited list of pre-approved tools. |
|
||||
|
||||
### Writing good descriptions
|
||||
|
||||
The description is critical — it's what the agent uses to decide whether to activate a skill. Be specific:
|
||||
|
||||
```yaml
|
||||
# Good — tells the agent what and when
|
||||
description: Extract text and tables from PDF files, fill PDF forms, and merge multiple PDFs. Use when working with PDF documents or when the user mentions PDFs, forms, or document extraction.
|
||||
|
||||
# Bad — too vague for the agent to match
|
||||
description: Helps with PDFs.
|
||||
```
|
||||
|
||||
### Writing good instructions
|
||||
|
||||
The markdown body is loaded into the agent's context when the skill is activated. Tips:
|
||||
|
||||
- **Be procedural**: Step-by-step instructions work better than abstract descriptions.
|
||||
- **Keep it focused**: Stay under 500 lines / 5000 tokens. Move detailed reference material to `references/`.
|
||||
- **Use relative paths**: Reference bundled files with relative paths (`scripts/run.py`, `references/guide.md`).
|
||||
- **Include examples**: Show sample inputs and expected outputs.
|
||||
- **Cover edge cases**: Tell the agent what to do when things go wrong.
|
||||
|
||||
## How skills are activated
|
||||
|
||||
Skills use **progressive disclosure** — three tiers that keep context usage efficient:
|
||||
|
||||
### Tier 1: Catalog (always loaded)
|
||||
|
||||
At session start, the agent sees a compact catalog of all available skills (name + description only, ~50-100 tokens each). This is how it knows what skills exist.
|
||||
|
||||
### Tier 2: Instructions (on demand)
|
||||
|
||||
When the agent determines a skill is relevant to the current task, it reads the full `SKILL.md` body into context. This happens automatically — the agent matches the task against skill descriptions and activates the best fit.
|
||||
|
||||
### Tier 3: Resources (on demand)
|
||||
|
||||
When skill instructions reference supporting files (`scripts/extract.py`, `references/api-docs.md`), the agent reads those individually as needed.
|
||||
|
||||
### Pre-activated skills
|
||||
|
||||
Some agents are configured to load specific skills at session start (skipping the catalog phase). This is set in the agent's configuration:
|
||||
|
||||
```python
|
||||
# In agent definition
|
||||
skills = ["code-review", "deep-research"]
|
||||
```
|
||||
|
||||
Pre-activated skills have their full instructions loaded from the start, without waiting for the agent to decide they're relevant.
|
||||
|
||||
## Trust and security
|
||||
|
||||
### Why trust gating exists
|
||||
|
||||
Project-level skills come from the repository being worked on. If you clone an untrusted repo that contains a `.hive/skills/` directory, those skills could inject instructions into the agent's system prompt. Trust gating prevents this.
|
||||
|
||||
**User-level and framework skills are always trusted.** Only project-scope skills go through trust gating.
|
||||
|
||||
### What happens with untrusted project skills
|
||||
|
||||
When Hive encounters project-level skills from a repo you haven't trusted before, it shows a consent prompt:
|
||||
|
||||
```
|
||||
============================================================
|
||||
SKILL TRUST REQUIRED
|
||||
============================================================
|
||||
|
||||
The project at /home/user/new-project wants to load 2 skill(s)
|
||||
that will inject instructions into the agent's system prompt.
|
||||
Source: github.com/org/new-project
|
||||
|
||||
Skills requesting access:
|
||||
• deploy-pipeline
|
||||
"Automated deployment workflow for this project."
|
||||
/home/user/new-project/.hive/skills/deploy-pipeline/SKILL.md
|
||||
• code-standards
|
||||
"Project-specific coding standards and review checklist."
|
||||
/home/user/new-project/.hive/skills/code-standards/SKILL.md
|
||||
|
||||
Options:
|
||||
1) Trust this session only
|
||||
2) Trust permanently — remember for future runs
|
||||
3) Deny — skip all project-scope skills from this repo
|
||||
────────────────────────────────────────────────────────────
|
||||
Select option (1-3):
|
||||
```
|
||||
|
||||
### Trust a repo via CLI
|
||||
|
||||
To trust a repo permanently without the interactive prompt:
|
||||
|
||||
```bash
|
||||
hive skill trust /path/to/project
|
||||
```
|
||||
|
||||
This stores the trust decision in `~/.hive/trusted_repos.json`, keyed by the normalized git remote URL (e.g., `github.com/org/repo`).
|
||||
|
||||
### Automatic trust
|
||||
|
||||
Some repos are trusted automatically:
|
||||
|
||||
- **No git repo**: Directories without `.git/` are always trusted.
|
||||
- **No remote**: Local-only git repos (no `origin` remote) are always trusted.
|
||||
- **Localhost remotes**: Repos with `localhost`/`127.0.0.1` remotes are always trusted.
|
||||
- **Own-remote patterns**: Repos matching patterns in `~/.hive/own_remotes` or the `HIVE_OWN_REMOTES` env var are always trusted.
|
||||
|
||||
### Configure own-remote patterns
|
||||
|
||||
If you trust all repos from your organization:
|
||||
|
||||
```bash
|
||||
# Via file (one pattern per line)
|
||||
echo "github.com/my-org/*" >> ~/.hive/own_remotes
|
||||
echo "gitlab.com/my-team/*" >> ~/.hive/own_remotes
|
||||
|
||||
# Via environment variable (comma-separated)
|
||||
export HIVE_OWN_REMOTES="github.com/my-org/*,github.com/my-corp/*"
|
||||
```
|
||||
|
||||
### CI / headless environments
|
||||
|
||||
In non-interactive environments, untrusted project skills are silently skipped. To trust them explicitly:
|
||||
|
||||
```bash
|
||||
export HIVE_TRUST_PROJECT_SKILLS=1
|
||||
hive run my-agent
|
||||
```
|
||||
|
||||
## Default skills
|
||||
|
||||
Hive ships with six built-in operational skills that provide runtime resilience. These are always loaded (unless disabled) and appear as "Operational Protocols" in the agent's system prompt.
|
||||
|
||||
| Skill | Purpose |
|
||||
|-------|---------|
|
||||
| `hive.note-taking` | Structured working notes in shared memory |
|
||||
| `hive.batch-ledger` | Track per-item status in batch operations |
|
||||
| `hive.context-preservation` | Save context before context window pruning |
|
||||
| `hive.quality-monitor` | Self-assess output quality periodically |
|
||||
| `hive.error-recovery` | Structured error classification and recovery |
|
||||
| `hive.task-decomposition` | Break complex tasks into subtasks |
|
||||
|
||||
### Disable default skills
|
||||
|
||||
In your agent configuration:
|
||||
|
||||
```python
|
||||
# Disable a specific default skill
|
||||
default_skills = {
|
||||
"hive.quality-monitor": {"enabled": False},
|
||||
}
|
||||
|
||||
# Disable all default skills
|
||||
default_skills = {
|
||||
"_all": {"enabled": False},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `HIVE_TRUST_PROJECT_SKILLS=1` | Bypass trust gating for all project-level skills (CI override) |
|
||||
| `HIVE_OWN_REMOTES` | Comma-separated glob patterns for auto-trusted remotes (e.g., `github.com/myorg/*`) |
|
||||
|
||||
## Compatibility with other agents
|
||||
|
||||
Skills written for any Agent Skills-compatible agent work in Hive:
|
||||
|
||||
- Place them in `.agents/skills/` (cross-client) or `.hive/skills/` (Hive-specific).
|
||||
- The `SKILL.md` format is identical across Claude Code, Cursor, Gemini CLI, and others.
|
||||
- Skills installed at `~/.agents/skills/` are visible to all compatible agents on your machine.
|
||||
|
||||
See the [Agent Skills specification](https://agentskills.io/specification) for the full format reference.
|
||||
Reference in New Issue
Block a user