A high-autonomy, general-purpose AI research and navigation agent built in Go, featuring a rich terminal TUI, Google ADK orchestration with Gemini, MCP server integration, a Python code interpreter, and a self-evolving skill library.
hakase is a terminal-based AI agent harness inspired by the Hermes Agent framework. It orchestrates multiple specialized sub-agents — a Web Researcher and a Code Interpreter — through a Google ADK root orchestrator, powered by Gemini models. The entire interaction happens inside a simple split-pane TUI built with Bubble Tea.
The agent can:
- 🔍 Browse & research the web using MCP-connected browser tools
- 📥 Download files, PDFs, and images from the internet
- 🐍 Execute Python code in an isolated virtual environment with auto-dependency resolution
- 📊 Analyze data, generate charts, and produce visual artifacts
- 🧠 Learn & persist skills — novel Python workflows are automatically saved to a local skill library for future reuse
- 📚 Manage a persistent knowledge base — wiki-style markdown notes with YAML frontmatter and [[wikilinks]] for durable facts the agent learns, with tools to save, recall, search, update, link, cite, and lint
- 🛡️ Sandboxed execution — subprocesses and file operations are confined to an approved workspace by default (path-confinement), with optional kernel-level bubblewrap isolation
- 💻 Run system commands — execute shell commands, scripts, and executables directly on the host via a
system_exectoolset - 📂 Manage outputs — generated HTML files, data artifacts, and more are saved to
./outputs/ - ⏰ Schedule recurring tasks — a
cronjobtool for one-shot and recurring agent tasks (research digests, monitoring, periodic reports) with cron/interval/ISO schedules, persisted to~/.hakase/cronjobs.jsonand fired by a background scheduler
hakase/
├── main.go # Entry point — loads config, boots the TUI and agent runner
├── agent.go # Core agent logic: ADK setup, sub-agents, tools (Python interpreter, downloader, skill manager)
├── cronjob.go # Scheduled tasks - cronjob tool, scheduler, ~/.hakase/cronjobs.json registry
├── delegate.go # Sub-agent delegation — execute_task, progress reporting, dedup cache, watchdog
├── toolcall.go # Malformed tool-call JSON repair and retry
├── sandbox.go # Workspace path confinement (root normalization, secure join, containment checks)
├── sandboxexec.go # bubblewrap (bwrap) subprocess isolation for sandboxed exec
├── systemexec.go # system_exec toolset — shell routing, process hardening, env scrubbing
├── fileops.go # File operation tools (read/write/patch/search) with sandbox-aware resolution
├── debug_log.go # Structured JSON debug logging (info/warn/error levels)
├── skill_discovery.go # Markdown & Python skill discovery/loading
├── instruction_context.go # Project context files (AGENTS.md) discovery & rendering
├── rule_cli.go # hakase rules CLI (list/show project context files)
├── ui.go # Bubble Tea TUI — split-pane layout with chat, log, and input views
├── config.go # Config loader (reads config.json)
├── config.json # Runtime configuration (API key, model, MCP server URL)
├── config.json.example # Example config template
├── go.mod / go.sum # Go module dependencies
├── skills/ # Persisted Python skill library
├── knowledge/ # Persistent knowledge base (markdown notes with YAML frontmatter + [[wikilinks]])
├── .agents/skills/ # Portable markdown skills (SKILL.md) - includes the hakase self-skill
├── skill_cli.go # hakase skill CLI (create/list/validate)
├── downloads/ # Downloaded files (PDFs, images, datasets)
├── outputs/ # Generated artifacts (HTML files, charts, reports)
└── .venv/ # Python virtual environment (auto-created)
A split-pane terminal interface built with Bubble Tea and Lip Gloss:
- Left panel — Chat viewport displaying agent responses and tool call logs
- Right panel — Real-time status and execution logs
- Bottom — Multi-line text input (auto-grows up to 3 lines) with focus cycling (
Tabto switch panes) and a hint bar showing the most used shortcuts - Mid-run messaging — Type and send while the agent is working: your message is queued (shown as
N queuedin the hint bar), steered into the running session at the next model-call boundary as aUSER INTERJECTION, then processed as its own turn when the current run completes - Mid-run questions — The agent can pause and ask you a question mid-task via the
clarifytool; choose from up to 4 options or type a free-text answer, [esc] to dismiss - Help overlay — Press
Ctrl+/(?when not typing) for a full keyboard shortcut reference
| Shortcut | Action |
|---|---|
Ctrl+C |
Quit the application (also cancels a running agent) |
Esc Esc |
Interrupt the running agent (double-press within 2s) |
Esc |
Close the help overlay (never quits) |
Ctrl+/ or ? |
Toggle the help overlay |
Tab / Shift+Tab |
Cycle focus: input → chat → log → task |
Ctrl+T |
Toggle the thinking display |
Enter |
Send the message (queued while the agent is busy) |
Shift+Enter / Ctrl+J |
Insert a newline in the input |
↑/k, ↓/j |
Scroll the focused pane (older/newer content) |
PgUp/b, PgDn/f |
Page up / down in the focused pane |
u / d |
Half page up / down in the focused pane |
Home/g, End/G |
Jump to top / bottom of the focused pane |
Ctrl+A / Ctrl+E |
Jump to line start / end in the input |
Ctrl+U |
Clear the input |
Mouse wheel scrolling works on whichever pane is focused. The log pane stays pinned to the bottom unless you scroll up to read history.
Type / in the input to see a filtered command menu (arrow keys navigate,
Tab completes, Enter runs). Built-in commands:
| Command | Action |
|---|---|
/board |
Task board: summary, list, new <title>, get <id>, update <id>, done <id>, fail <id>, cancel <id>, delete <id>, archive <id>, claim <id> |
/compact [focus] |
Summarize the conversation to free context, continuing the same session; optional focus instructions steer the summary |
/new |
Start a fresh session (previous sessions stay resumable) |
/sessions |
Open the session chooser to switch or resume old sessions |
/help |
Show the keyboard shortcut and slash command reference |
/exit / /quit |
Exit hakase |
/compact runs the deterministic history snip immediately (keeps the last
two turns) and schedules an async LLM summary - the same compaction cascade
used by automatic context management (summary_model), exposed manually.
Attach files and images to a message without the agent having to find them:
@file- type@to open a workspace file picker; arrow keys navigate,Enterattaches the highlighted file as a chip (@name.go). Text files embed their content; images embed as multimodal input.- Image paste - copy an image (screenshot) and press
Ctrl+V; it is read from the clipboard and attached as a[image 1]chip. Text pastes still work normally. - Chips render in a row above the input;
Backspaceon an empty input removes the last chip. Attachments are sent alongside the prompt text and are persisted with the session (re-attached on resume).
Sandbox note: @ paths resolve through the sandbox read roots - files
outside the approved workspace are rejected with a hint.
Powered by Google ADK:
| Agent | Role |
|---|---|
| orchestrator | Root agent that delegates tasks to sub-agents based on intent |
| web_researcher | Searches the web, navigates pages, downloads files, extracts content |
| code_interpreter | Executes Python, performs data analysis, manages the skill library |
| general_purpose | Reads, writes, edits, and searches files in the workspace |
- Runs Python code in an isolated
.venvvirtual environment - Auto-resolves missing dependencies — detects
ModuleNotFoundError, installs the package via pip, and retries - Sets
PYTHONPATHto include./skillsso persisted skills are importable - Sandbox-aware — when the sandbox is active, the script temp dir and working directory are pinned to the workspace root (
.hakase-tmp/) so script writes stay inside the approved workspace - Process hardening — the interpreter runs in its own process group with a parent-death signal, so children (and grandchildren) are reaped if the agent crashes
The agent can save tested Python scripts as reusable skills:
- Code is executed and verified via
python_interpreter - The agent calls
save_skillto persist the script to./skills/ - Skills are registered in
skills/skills.jsonwith name, description, and import usage - On subsequent runs, the agent loads all saved skills and can reuse them via
from skills.<name> import ...
The agent maintains a persistent, wiki-style knowledge base for durable facts it learns. Notes are markdown files with YAML frontmatter and [[wikilinks]], stored in a workspace folder (default ./knowledge/, configurable via knowledge_dir in config.json):
knowledge/
├── index.md # auto-maintained catalog of all notes (regenerated on every change)
├── log.md # append-only operation log ("## [date] action | Title")
├── notes/ # optional subdirectory (preferred when a slug exists in both places)
└── raw/ # optional immutable raw sources (excluded from the index)
Each note is markdown with YAML frontmatter: title, aliases, tags, created, updated, status (draft / permanent / archived), confidence (high / medium / low), sources (URLs or raw/ paths), summary, and related. The body contains [[wikilinks]] to related notes.
The orchestrator agent exposes eight knowledge tools:
save_knowledge- save a new note; unresolved[[wikilinks]]are reported as danglingrecall_knowledge- load a note by slug, basename, or alias, with backlinkssearch_knowledge- keyword/tag search across notesupdate_knowledge- correct or extend an existing notelink_knowledge- create[[wikilinks]]between notescite_knowledge- footnote-style citation of a note with its source URLlist_knowledge- list all noteslint_knowledge- health check: orphans, dangling links, broken index
Wiki links support [[target]], [[target|label]], and [[target#heading]]. Resolution is case-insensitive (slug -> unique basename -> alias). Links to notes that do not exist yet are reported as dangling links; the agent surfaces them to the user and offers to create them, creating only after user confirmation.
Retrieval is keyword/tag/grep only - no embeddings, no vector database, no extra dependencies.
The hakase knowledge CLI manages the knowledge base:
hakase knowledge list|read|search|lint|create|link- with a--dirflag to override the knowledge directory
hakase knowledge create "Quantum Computing" --tags physics --content "See [[Superposition]]."
hakase knowledge read quantum-computing
hakase knowledge lintThe hakase cron command manages scheduled tasks:
hakase cron list|status|pause <id>|resume <id>|run <id>|tick- list all jobs, show the registry path and state counts, pause/resume a job by ID or name, trigger a job immediately, or run all due jobs once
The in-process scheduler runs while the TUI is open (a 30-second tick fires due jobs headless); hakase cron tick runs all due jobs once from the CLI. run and tick bootstrap the model for headless execution; the other subcommands are pure file operations.
hakase loads project context files - AGENTS.md - into every agent's system instruction, so repository conventions, architecture notes, and coding rules are followed without being repeated in every prompt. The semantics match the conventions used by OpenCode and Hermes Agent, so context files authored for those agents work unchanged:
- Project scope -
AGENTS.mdfiles are collected from the current directory up to the git root (closest first; nested files stack). Only when noAGENTS.mdexists anywhere in the walk is a project-scopedCLAUDE.mdused as a fallback. - User scope -
~/.hakase/AGENTS.md(or$HAKASE_HOME/AGENTS.md) when present. The Claude Code global~/.claude/CLAUDE.mdis deliberately never loaded. - Custom files -
instruction_filesinconfig.jsonadds more context: absolute paths,~/-prefixed paths, project-relative paths, orhttp(s)://URLs (fetched at startup with a short timeout; failures are skipped, never fatal).
Each loaded file is rendered as Instructions from: <path> followed by its content under a ### PROJECT CONTEXT FILES: header. Content is prompt-injection scanned (matching files are blocked and replaced with a warning) and truncated per file (Hermes-style 70% head / 20% tail split, default 20,000 characters, configurable via context_files.max_chars). The rendered block is accounted for in the context-compaction token budget, so large files cannot silently blow the model window.
The block is injected into the orchestrator and all sub-agents by default; context_files.apply_to restricts it to a named subset (orchestrator, web_researcher, code_interpreter, general_purpose).
{
"instruction_files": ["docs/rules.md", "https://example.com/team-agents.md"],
"context_files": {
"max_chars": 20000,
"apply_to": ["orchestrator", "general_purpose"]
}
}Beyond the startup block, reading a file (read_file) or searching a directory (search_files) below the workspace root attaches any AGENTS.md in that directory tree - not already in the system prompt - to the tool result under a SUBDIRECTORY CONTEXT header. Each file is attached once per session, injection-scanned, and capped at 8,000 characters per file. This keeps deep-nested conventions in the model's view without bloating the system prompt.
If a loaded context file changes mid-session, hakase detects it (cheap path/size/mtime fingerprint, checked before every model call) and injects a one-shot PROJECT CONTEXT UPDATE notice so the model follows the updated instructions.
Preview the active context without running the agent:
hakase rules list # list the context files that would be loaded (render order + scope)
hakase rules show AGENTS.md # show one file's content (path or basename)- Downloads files from any HTTP/HTTPS URL
- Saves to
./downloads/with automatic filename resolution - Supports PDFs, images, datasets, and binary blobs
- Filename sanitized — a supplied filename is stripped to its base component (
filepath.Base) so../traversal attempts are neutralized - Sandbox-aware — when the sandbox is active, the download target is resolved through workspace confinement and rejected if it would land outside the approved workspace
The vision tool loads an image (URL, local file path, or data: URL) so the model can see it. When the main model supports images (Gemini models), the image is attached directly to the model context; otherwise a configured vision_model describes the image as text. Images are SSRF-guarded, size-capped, and auto-converted or resized to fit provider limits. Configure via vision_model, vision_provider, vision_base_url, vision_api_key, and model_vision.
Attached images (@file or pasted screenshots) are handled the same way: on a non-vision main model they are described by vision_model before reaching the model (required on OpenAI-compatible providers, whose adapter rejects raw image parts); on a vision-capable model they pass through as inline input.
The general_purpose agent provides workspace file tools:
read_file— read file contents, optionally restricted to a line range (offset/limit)write_file— create new files (or overwrite existing ones withoverwrite=true)patch— targeted string replacement inside an existing filesearch_files— recursive regex search over file contents withcontent/files_with_matches/countoutput modes
Search is hardened against pathological trees: head_limit defaults to 100 when unset, the walk visits at most 50,000 entries, and a per-call 30s deadline gracefully returns partial matches (marked truncated) instead of hanging. When the sandbox is active, all four tools resolve paths through workspace confinement (reads confined to read roots, writes to workspace roots).
A system_exec toolset runs shell commands, scripts, and executables directly on the host machine, with several safety guarantees:
- Shell routing — when no
argsare provided the whole command line is passed tosh -c, so pipes, redirects, globs,&&/||, and compound commands work naturally; explicit(command, args...)calls keep full control - Process hardening — spawned processes are placed in their own process group with a parent-death signal, so they and their children are reaped if the agent dies
- Path confinement (all sandbox modes) — when the sandbox is active, absolute path arguments in the command line are audited against the sandbox read roots and trusted system dirs (
/usr,/lib,/bin,/etc,/proc,/dev,/sys,/tmp,/run); anything else is rejected with an actionable error. This stops whole-filesystem scans likefind / -type d -name skillsfrom escaping the workspace. Add directories tosandbox.read_rootsinconfig.jsonto permit them. - Default timeout — synchronous
system_execkills the command after 120s whentimeout_secondsis omitted, so a hung command can never block the agent indefinitely. Long-running work should usesystem_exec_start(background) or an explicittimeout_seconds. - Sandbox integration — under a
bubblewrapsandbox the command is wrapped inbwrapwith filesystem + network isolation; sensitive env vars (HAKASE_*,AWS_*,GITHUB_*,OPENAI_*) are scrubbed so they never leak into sandboxed subprocesses; the working directory is pinned to the workspace root
hakase confines subprocesses and file operations to approved workspaces out of the box. The sandbox block in config.json selects a strategy:
| Mode | Description |
|---|---|
paths (default) |
Pure path confinement — all file ops (read_file/write_file/patch/search_files), downloads, and the Python interpreter resolve paths against approved read/work/deny roots. system_exec commands are audited so absolute path arguments must stay under the read roots or trusted system dirs. Symlink escapes are prevented via securejoin + EvalSymlinks re-verification. |
bubblewrap |
Adds kernel-level subprocess isolation — system_exec and Python runs are wrapped in bubblewrap (bwrap) with separate PID/IPC/UTS/user namespaces, dropped capabilities, minimal filesystems, read-only system dirs, and optional network unshare. |
landlock |
Reserved for future in-process Landlock + seccomp confinement (Phase 3). |
off |
Explicitly disables confinement (opt-in only). |
Key properties:
- On by default — an absent or unset
sandboxblock yieldspathsmode, so the agent cannot write outside approved workspaces without explicit configuration - Roots —
workspace_roots(writable, default["."]),read_roots(readable, default = workspace roots), anddeny_roots(always rejected, highest precedence); all are symlink-evaluated and de-duplicated - Downloads — the filename is basename-sanitized and the output path is confined to the workspace
- Current sandbox — if
bwrapis not installed, bubblewrap mode falls back to the safe path-confinement exec path and logs a warning
Connects to an MCP (Model Context Protocol) server for browser automation and web navigation tools. Configured via config.json → mcp_server_url.
- Go 1.26+
- Python 3 — required for the code interpreter (
.venvexecution, auto-dependency resolution) and the self-evolving skill library (./skills/) - A Google Gemini API key
- Lightpanda — the MCP browser automation server that provides web navigation tools. Install it from lightpanda.ai and start it before running the agent (it serves the MCP endpoint on
localhost:9223by default)
- Clone and configure:
cp config.json.example config.json
# Edit config.json with your API key and MCP server URL- Install Go dependencies:
go mod download- Run the agent:
go run .The TUI will launch. Type your question and press Enter. The agent will research, analyze, and respond — all from your terminal.
hakase is currently being developed and tested on Linux.
Edit config.json:
{
"provider": "gemini",
"model_name": "gemini-3.5-flash-lite",
"api_key": "your-gemini-api-key",
"instruction": "You are a web automation agent harness.",
"mcp_server_url": "http://localhost:9223/mcp",
"knowledge_dir": "",
"sandbox": {
"mode": "paths"
}
}hakase supports multiple LLM providers, selected via the provider field in config.json. An empty or missing provider value defaults to gemini, preserving previous behavior.
| Provider | Description | Default Model |
|---|---|---|
gemini |
Google Gemini | gemini-2.5-flash |
openai |
OpenAI API | gpt-4o-mini |
openai-compatible |
OpenAI-compatible endpoints (Ollama, vLLM, etc.) | gpt-4o-mini |
When model_name is empty, the provider's default model is used.
Gemini (default):
{
"provider": "gemini",
"model_name": "gemini-2.5-flash",
"api_key": "your_gemini_api_key",
"instruction": "You are a web automation agent harness.",
"mcp_server_url": "http://localhost:9223/mcp",
"fallback_providers": ["openai"],
"base_url": "",
"provider_options": {}
}OpenAI:
{
"provider": "openai",
"model_name": "gpt-4o-mini",
"api_key": "your_openai_api_key",
"instruction": "You are a web automation agent harness.",
"mcp_server_url": "http://localhost:9223/mcp"
}OpenAI-compatible endpoint (e.g. Ollama):
{
"provider": "openai-compatible",
"model_name": "llama-3.3-70b",
"api_key": "optional_key",
"base_url": "http://localhost:11434/v1",
"instruction": "You are a web automation agent harness.",
"mcp_server_url": "http://localhost:9223/mcp"
}base_url— Base URL for OpenAI-compatible endpoints (e.g.http://localhost:11434/v1for Ollama). Ignored when empty; used only by theopenai/openai-compatibleproviders.fallback_providers— Optional ordered list of provider names to try if the primary provider fails (e.g.["openai"]). Empty by default.provider_options— Optional map of provider-specific settings. Reserved for future use.instruction- Optional, additional customization rendered into the agent instructions as aUSER CONFIG INSTRUCTIONsection (alongside the discoveredAGENTS.mdcontext). It is not a replacement for the built-in system prompts - it only adds.instruction_files- Optional list of extra context files merged into the project context (see Project Context Files): absolute paths,~/-prefixed paths, project-relative paths, orhttp(s)://URLs.context_files- Optional tuning for the project context files:max_chars(per-file truncation cap, default20000) andapply_to(restrict which agents receive the block; empty = all).knowledge_dir- Directory for the persistent knowledge base (default./knowledge; a leading~expands to the user home, e.g.~/.hakase/knowledgefor a user-global base).summary_model— Optional cheaper/weaker model used for context-compaction summarization (e.g.gemini-2.5-flash-lite). When empty, the primary model handles summaries. SetHAKASE_SUMMARY_MODELto override via environment.vision_model- Optional multimodal model used to describe images as text when the main model lacks vision (legacy mode). Empty = disabled.vision_base_url- Optional separate endpoint for the vision model; empty = primarybase_url.vision_api_key- Optional separate key for the vision model; empty = primaryapi_key.vision_provider- Optional provider for the vision model:gemini,openai, oropenai-compatible. Empty = primary provider (avision_base_urlalone still forces an OpenAI-compatible endpoint). Use this when the vision model lives on a different backend than the main model - e.g. a Gemini vision model while the primary provider is OpenAI-compatible.model_vision- Override multimodal detection for the main model:auto|yes|no(defaultauto).sandbox— Optional confinement block (see Sandboxing & Workspace Confinement). Absent →pathsmode. Fields:mode(paths|bubblewrap|landlock|off),workspace_roots,read_roots,deny_roots,allow_network,allow_pip_install,permissions.loop_guard— Optional anti-degeneration guardrails that abort a run stuck in a repetition loop or text-only bloat instead of burning the whole context/output window. Zero values use the defaults. Fields:max_output_tokens(cap on providermaxOutputTokens, default8192),repetition_limit(abort after this many consecutive identical non-thought chunks, default8),max_text_without_tool(abort after this many runes of text with zero tool calls, default20000). SetHAKASE_MAX_OUTPUT_TOKENSto override the cap via environment.
Environment variables override the matching config.json fields, with environment variables taking precedence over the file. If config.json is missing but at least one of these is set, the config is built entirely from the environment:
| Variable | Overrides |
|---|---|
HAKASE_API_KEY |
api_key |
HAKASE_PROVIDER |
provider |
HAKASE_MODEL |
model_name |
HAKASE_BASE_URL |
base_url |
HAKASE_SUMMARY_MODEL |
summary_model |
HAKASE_VISION_MODEL |
vision_model |
HAKASE_VISION_BASE_URL |
vision_base_url |
HAKASE_VISION_API_KEY |
vision_api_key |
HAKASE_VISION_PROVIDER |
vision_provider |
HAKASE_MODEL_VISION |
model_vision |
HAKASE_DEBUG |
debug |
HAKASE_MAX_OUTPUT_TOKENS |
loop_guard.max_output_tokens |
HAKASE_HOME |
user home directory (default ~/.hakase) |
Note: HAKASE_* variables are scrubbed from the environment of subprocesses spawned by the agent (see system_exec), so the API key used for providers never leaks into shell commands or sandboxed Python runs.
User-level agent state lives under ~/.hakase/ (Claude-style; override with $HAKASE_HOME):
~/.hakase/config.json- user-level config fallback, used when no projectconfig.jsonexists~/.hakase/skills/- user-level markdown skills, discovered automatically~/.hakase/knowledge/- optional user-global knowledge base (setknowledge_dir: "~/.hakase/knowledge")
hakase migrated from the ADK v1 stack to the ADK v2 stack (google.golang.org/adk/v2). The configuration format is unchanged, so existing config.json files continue to work without modification (backward compatible). An empty provider field still selects Gemini, matching the previous single-provider behavior.
- Unsupported provider error —
unsupported provider: <name>means theproviderfield is set to a value other thangemini,openai, oropenai-compatible. Correct the value or leave it empty to use the default. - Empty API key error —
gemini provider requires an api_keyoropenai provider requires an api_keymeans theapi_keyfield is missing for the selected provider. Set a valid key inconfig.json. - OpenAI-compatible endpoint unreachable — when using
openai-compatible, confirm the server atbase_urlis running and serves an OpenAI-compatible API (e.g. Ollama athttp://localhost:11434/v1), and that it is reachable from the machine running the agent.
"Summarize the latest developments in quantum computing and provide key citations."
The orchestrator delegates to web_researcher, which navigates sources and returns a synthesized Markdown answer.
"Create a fully playable browser game as a single HTML file."
The code_interpreter writes a self-contained HTML+JS game, saves it to ./outputs/, and persists the script as a reusable skill in ./skills/.
"Download this CSV, compute summary statistics, and generate a chart."
The agent downloads the file, runs Python with pandas/matplotlib in .venv, and saves the output artifact.
hakase supports markdown-based skills in addition to Python skills. Each skill is a directory containing a SKILL.md file with YAML frontmatter and a progressive-disclosure body.
The hakase skill command manages markdown skills:
hakase skill create <name> [--dir <path>] [--description <text>] [--template python] [--force]- Scaffolds<dir>/<name>/SKILL.mdwith valid frontmatter (name,description,license: MIT,metadata: author/version) plusscripts/andreferences/subdirectories. The<name>must match^[a-z0-9]+(-[a-z0-9]+)*$. The default directory is the git project root's.agents/skills/. The description falls back to a non-empty placeholder so the skill passes validation immediately. The--template pythonflag also writesscripts/<name>.py. Fails on an existing directory unless--forceis used.hakase skill list- Prints discovered skills (Python from./skills/skills.jsonplus markdown from project and user directories) with source paths.hakase skill validate <dir>- Parses and validates a single skill; exits non-zero on failure (CI-friendly).
Each skill directory contains:
SKILL.md- Required. YAML frontmatter (nameanddescriptionare required;license,compatibility,metadata, andallowed-toolsare optional) followed by a progressive-disclosure body.scripts/- Optional executable code files.references/- Optional deeper documentation (loaded on demand).
Skills are discovered from these locations, in priority order (project first, deduped by name, first match wins):
- Project level (walk from cwd up to the git root):
.agents/skills/,.claude/skills/,.opencode/skills/,.gemini/skills/ - Project library:
./skills(existing Python skill library dir;SKILL.mdfiles are also scanned here) - Custom dirs:
skill_dirsfromconfig.json(resolved against the project root when relative) - User level:
~/.hakase/skills/(or$HAKASE_HOME/skills/),~/.agents/skills/,~/.claude/skills/,~/.gemini/skills/,~/.config/opencode/skills/(honoringXDG_CONFIG_HOME)
Skills are indexed by name and description in the agent prompt. The full body is loaded on demand via the load_markdown_skill tool. Invalid skills are skipped with a warning. Each markdown skill listing in the prompt includes its discovery source directory (e.g. Location: <root>/.agents/skills), so the agent knows where existing skills actually live.
When the agent creates a new markdown skill, the prompt instructs it to prefer the project root's .agents/skills/ (the portable, always-scanned location, and the default target of hakase skill create). If writing there fails, it may fall back to any other valid discovery location in priority order - the project's .claude/skills/, .opencode/skills/, or .gemini/skills/, then the user-level ~/.hakase/skills/, ~/.agents/skills/, ~/.claude/skills/, ~/.gemini/skills/, or ~/.config/opencode/skills/. Skills placed outside these discovery paths are never loaded, and the skill directory name must match the name in its SKILL.md frontmatter.
The repository ships a self-knowledge skill at .agents/skills/hakase/SKILL.md that documents the agent itself: identity, architecture, sub-agents, tools, configuration, skills system, knowledge base, sandbox/safety model, user home (~/.hakase), CLI commands, and troubleshooting. The agent loads it whenever the user asks about hakase itself ("who are you", "what can you do", "how do I configure/extend hakase"). Deeper reference material lives in .agents/skills/hakase/references/ (architecture, configuration, skills, knowledge-base, troubleshooting). Being committed to the repository, it ships with the agent and is versioned with it; for binary-only installs it can be fetched into any discovery location (e.g. project .agents/skills/ or user ~/.hakase/skills/) from the GitHub repo, following the cross-tool gh skill install convention.
Skills authored to this format (e.g. from Claude Code, Codex CLI, Gemini CLI, or OpenCode - the agentskills.io spec) work in hakase by dropping them into .agents/skills/.
Python skills (skills.json + .py files) are unchanged. On a name collision, the markdown skill wins in the prompt and the Python entry is omitted with a logged warning (the .py file remains importable).
- Skills added mid-session require a restart to be discovered.
.agents/skills/is meant to be committed to the repository.
| Package | Purpose |
|---|---|
charm.land/bubbletea/v2 |
TUI framework |
charm.land/bubbles/v2 |
TUI components (text input, viewport) |
charm.land/lipgloss/v2 |
Terminal styling |
google.golang.org/adk/v2 |
Google Agent Development Kit (v2; was v1) |
google.golang.org/genai |
Gemini AI client |
github.com/openai/openai-go/v3 |
OpenAI API client |
github.com/modelcontextprotocol/go-sdk |
MCP client for browser automation |
github.com/cyphar/filepath-securejoin |
Symlink-safe secure path joining for sandbox confinement |
github.com/robfig/cron/v3 |
5-field cron parsing for scheduled tasks |
MIT