Full Code of NousResearch/hermes-agent for AI

main b748fcf83689 cached
1064 files
15.9 MB
4.2M tokens
11130 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (16,825K chars total). Download the full file to get everything.
Repository: NousResearch/hermes-agent
Branch: main
Commit: b748fcf83689
Files: 1064
Total size: 15.9 MB

Directory structure:
gitextract_1adcsei_/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── setup_help.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── deploy-site.yml
│       ├── docs-site-checks.yml
│       └── tests.yml
├── .gitignore
├── .gitmodules
├── .plans/
│   ├── openai-api-server.md
│   └── streaming-support.md
├── AGENTS.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASE_v0.2.0.md
├── RELEASE_v0.3.0.md
├── acp_adapter/
│   ├── __init__.py
│   ├── __main__.py
│   ├── auth.py
│   ├── entry.py
│   ├── events.py
│   ├── permissions.py
│   ├── server.py
│   ├── session.py
│   └── tools.py
├── acp_registry/
│   └── agent.json
├── agent/
│   ├── __init__.py
│   ├── anthropic_adapter.py
│   ├── auxiliary_client.py
│   ├── context_compressor.py
│   ├── copilot_acp_client.py
│   ├── display.py
│   ├── insights.py
│   ├── model_metadata.py
│   ├── models_dev.py
│   ├── prompt_builder.py
│   ├── prompt_caching.py
│   ├── redact.py
│   ├── skill_commands.py
│   ├── smart_model_routing.py
│   ├── title_generator.py
│   ├── trajectory.py
│   └── usage_pricing.py
├── batch_runner.py
├── cli-config.yaml.example
├── cli.py
├── cron/
│   ├── __init__.py
│   ├── jobs.py
│   └── scheduler.py
├── datagen-config-examples/
│   ├── example_browser_tasks.jsonl
│   ├── run_browser_tasks.sh
│   ├── trajectory_compression.yaml
│   └── web_research.yaml
├── docs/
│   ├── acp-setup.md
│   ├── honcho-integration-spec.html
│   ├── honcho-integration-spec.md
│   ├── migration/
│   │   └── openclaw.md
│   ├── plans/
│   │   └── 2026-03-16-pricing-accuracy-architecture-design.md
│   └── skins/
│       └── example-skin.yaml
├── environments/
│   ├── README.md
│   ├── __init__.py
│   ├── agent_loop.py
│   ├── agentic_opd_env.py
│   ├── benchmarks/
│   │   ├── __init__.py
│   │   ├── tblite/
│   │   │   ├── README.md
│   │   │   ├── __init__.py
│   │   │   ├── default.yaml
│   │   │   ├── local.yaml
│   │   │   ├── local_vllm.yaml
│   │   │   ├── run_eval.sh
│   │   │   └── tblite_env.py
│   │   ├── terminalbench_2/
│   │   │   ├── __init__.py
│   │   │   ├── default.yaml
│   │   │   ├── run_eval.sh
│   │   │   └── terminalbench2_env.py
│   │   └── yc_bench/
│   │       ├── README.md
│   │       ├── __init__.py
│   │       ├── default.yaml
│   │       ├── run_eval.sh
│   │       └── yc_bench_env.py
│   ├── hermes_base_env.py
│   ├── hermes_swe_env/
│   │   ├── __init__.py
│   │   ├── default.yaml
│   │   └── hermes_swe_env.py
│   ├── patches.py
│   ├── terminal_test_env/
│   │   ├── __init__.py
│   │   ├── default.yaml
│   │   └── terminal_test_env.py
│   ├── tool_call_parsers/
│   │   ├── __init__.py
│   │   ├── deepseek_v3_1_parser.py
│   │   ├── deepseek_v3_parser.py
│   │   ├── glm45_parser.py
│   │   ├── glm47_parser.py
│   │   ├── hermes_parser.py
│   │   ├── kimi_k2_parser.py
│   │   ├── llama_parser.py
│   │   ├── longcat_parser.py
│   │   ├── mistral_parser.py
│   │   ├── qwen3_coder_parser.py
│   │   └── qwen_parser.py
│   ├── tool_context.py
│   └── web_research_env.py
├── gateway/
│   ├── __init__.py
│   ├── channel_directory.py
│   ├── config.py
│   ├── delivery.py
│   ├── hooks.py
│   ├── mirror.py
│   ├── pairing.py
│   ├── platforms/
│   │   ├── ADDING_A_PLATFORM.md
│   │   ├── __init__.py
│   │   ├── api_server.py
│   │   ├── base.py
│   │   ├── dingtalk.py
│   │   ├── discord.py
│   │   ├── email.py
│   │   ├── homeassistant.py
│   │   ├── matrix.py
│   │   ├── mattermost.py
│   │   ├── signal.py
│   │   ├── slack.py
│   │   ├── sms.py
│   │   ├── telegram.py
│   │   ├── webhook.py
│   │   └── whatsapp.py
│   ├── run.py
│   ├── session.py
│   ├── status.py
│   ├── sticker_cache.py
│   └── stream_consumer.py
├── hermes
├── hermes_cli/
│   ├── __init__.py
│   ├── auth.py
│   ├── banner.py
│   ├── callbacks.py
│   ├── checklist.py
│   ├── claw.py
│   ├── clipboard.py
│   ├── codex_models.py
│   ├── colors.py
│   ├── commands.py
│   ├── config.py
│   ├── copilot_auth.py
│   ├── cron.py
│   ├── curses_ui.py
│   ├── default_soul.py
│   ├── doctor.py
│   ├── env_loader.py
│   ├── gateway.py
│   ├── main.py
│   ├── models.py
│   ├── pairing.py
│   ├── plugins.py
│   ├── runtime_provider.py
│   ├── setup.py
│   ├── skills_config.py
│   ├── skills_hub.py
│   ├── skin_engine.py
│   ├── status.py
│   ├── tools_config.py
│   └── uninstall.py
├── hermes_constants.py
├── hermes_state.py
├── hermes_time.py
├── honcho_integration/
│   ├── __init__.py
│   ├── cli.py
│   ├── client.py
│   └── session.py
├── landingpage/
│   ├── index.html
│   ├── script.js
│   └── style.css
├── mini_swe_runner.py
├── minisweagent_path.py
├── model_tools.py
├── optional-skills/
│   ├── DESCRIPTION.md
│   ├── autonomous-ai-agents/
│   │   ├── DESCRIPTION.md
│   │   └── blackbox/
│   │       └── SKILL.md
│   ├── blockchain/
│   │   ├── base/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── base_client.py
│   │   └── solana/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── solana_client.py
│   ├── creative/
│   │   └── blender-mcp/
│   │       └── SKILL.md
│   ├── email/
│   │   └── agentmail/
│   │       └── SKILL.md
│   ├── health/
│   │   ├── DESCRIPTION.md
│   │   └── neuroskill-bci/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── api.md
│   │           ├── metrics.md
│   │           └── protocols.md
│   ├── mcp/
│   │   ├── DESCRIPTION.md
│   │   └── fastmcp/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── fastmcp-cli.md
│   │       ├── scripts/
│   │       │   └── scaffold_fastmcp.py
│   │       └── templates/
│   │           ├── api_wrapper.py
│   │           ├── database_server.py
│   │           └── file_processor.py
│   ├── migration/
│   │   ├── DESCRIPTION.md
│   │   └── openclaw-migration/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── openclaw_to_hermes.py
│   ├── productivity/
│   │   └── telephony/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── telephony.py
│   ├── research/
│   │   └── qmd/
│   │       └── SKILL.md
│   └── security/
│       ├── 1password/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── cli-examples.md
│       │       └── get-started.md
│       ├── DESCRIPTION.md
│       ├── oss-forensics/
│       │   ├── SKILL.md
│       │   ├── references/
│       │   │   ├── evidence-types.md
│       │   │   ├── github-archive-guide.md
│       │   │   ├── investigation-templates.md
│       │   │   └── recovery-techniques.md
│       │   ├── scripts/
│       │   │   └── evidence-store.py
│       │   └── templates/
│       │       ├── forensic-report.md
│       │       └── malicious-package-report.md
│       └── sherlock/
│           └── SKILL.md
├── package.json
├── pyproject.toml
├── requirements.txt
├── rl_cli.py
├── run_agent.py
├── scripts/
│   ├── discord-voice-doctor.py
│   ├── hermes-gateway
│   ├── install.cmd
│   ├── install.ps1
│   ├── install.sh
│   ├── kill_modal.sh
│   ├── release.py
│   ├── sample_and_compress.py
│   └── whatsapp-bridge/
│       ├── bridge.js
│       └── package.json
├── setup-hermes.sh
├── skills/
│   ├── apple/
│   │   ├── DESCRIPTION.md
│   │   ├── apple-notes/
│   │   │   └── SKILL.md
│   │   ├── apple-reminders/
│   │   │   └── SKILL.md
│   │   ├── findmy/
│   │   │   └── SKILL.md
│   │   └── imessage/
│   │       └── SKILL.md
│   ├── autonomous-ai-agents/
│   │   ├── DESCRIPTION.md
│   │   ├── claude-code/
│   │   │   └── SKILL.md
│   │   ├── codex/
│   │   │   └── SKILL.md
│   │   ├── hermes-agent/
│   │   │   └── SKILL.md
│   │   └── opencode/
│   │       └── SKILL.md
│   ├── creative/
│   │   ├── DESCRIPTION.md
│   │   ├── ascii-art/
│   │   │   └── SKILL.md
│   │   ├── ascii-video/
│   │   │   ├── README.md
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       ├── architecture.md
│   │   │       ├── composition.md
│   │   │       ├── effects.md
│   │   │       ├── inputs.md
│   │   │       ├── optimization.md
│   │   │       ├── scenes.md
│   │   │       ├── shaders.md
│   │   │       └── troubleshooting.md
│   │   └── excalidraw/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   ├── colors.md
│   │       │   ├── dark-mode.md
│   │       │   └── examples.md
│   │       └── scripts/
│   │           └── upload.py
│   ├── data-science/
│   │   ├── DESCRIPTION.md
│   │   └── jupyter-live-kernel/
│   │       └── SKILL.md
│   ├── diagramming/
│   │   └── DESCRIPTION.md
│   ├── dogfood/
│   │   ├── SKILL.md
│   │   ├── hermes-agent-setup/
│   │   │   └── SKILL.md
│   │   ├── references/
│   │   │   └── issue-taxonomy.md
│   │   └── templates/
│   │       └── dogfood-report-template.md
│   ├── domain/
│   │   └── DESCRIPTION.md
│   ├── email/
│   │   ├── DESCRIPTION.md
│   │   └── himalaya/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── configuration.md
│   │           └── message-composition.md
│   ├── feeds/
│   │   └── DESCRIPTION.md
│   ├── gaming/
│   │   ├── DESCRIPTION.md
│   │   ├── minecraft-modpack-server/
│   │   │   └── SKILL.md
│   │   └── pokemon-player/
│   │       └── SKILL.md
│   ├── gifs/
│   │   └── DESCRIPTION.md
│   ├── github/
│   │   ├── DESCRIPTION.md
│   │   ├── codebase-inspection/
│   │   │   └── SKILL.md
│   │   ├── github-auth/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── gh-env.sh
│   │   ├── github-code-review/
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       └── review-output-template.md
│   │   ├── github-issues/
│   │   │   ├── SKILL.md
│   │   │   └── templates/
│   │   │       ├── bug-report.md
│   │   │       └── feature-request.md
│   │   ├── github-pr-workflow/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   ├── ci-troubleshooting.md
│   │   │   │   └── conventional-commits.md
│   │   │   └── templates/
│   │   │       ├── pr-body-bugfix.md
│   │   │       └── pr-body-feature.md
│   │   └── github-repo-management/
│   │       ├── SKILL.md
│   │       └── references/
│   │           └── github-api-cheatsheet.md
│   ├── index-cache/
│   │   ├── anthropics_skills_skills_.json
│   │   ├── claude_marketplace_anthropics_skills.json
│   │   ├── lobehub_index.json
│   │   └── openai_skills_skills_.json
│   ├── inference-sh/
│   │   ├── DESCRIPTION.md
│   │   └── cli/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── app-discovery.md
│   │           ├── authentication.md
│   │           ├── cli-reference.md
│   │           └── running-apps.md
│   ├── leisure/
│   │   └── find-nearby/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── find_nearby.py
│   ├── mcp/
│   │   ├── DESCRIPTION.md
│   │   ├── mcporter/
│   │   │   └── SKILL.md
│   │   └── native-mcp/
│   │       └── SKILL.md
│   ├── media/
│   │   ├── DESCRIPTION.md
│   │   ├── gif-search/
│   │   │   └── SKILL.md
│   │   ├── heartmula/
│   │   │   └── SKILL.md
│   │   ├── songsee/
│   │   │   └── SKILL.md
│   │   └── youtube-content/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── output-formats.md
│   │       └── scripts/
│   │           └── fetch_transcript.py
│   ├── mlops/
│   │   ├── DESCRIPTION.md
│   │   ├── cloud/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── lambda-labs/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   └── modal/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── advanced-usage.md
│   │   │           └── troubleshooting.md
│   │   ├── evaluation/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── huggingface-tokenizers/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── algorithms.md
│   │   │   │       ├── integration.md
│   │   │   │       ├── pipeline.md
│   │   │   │       └── training.md
│   │   │   ├── lm-evaluation-harness/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api-evaluation.md
│   │   │   │       ├── benchmark-guide.md
│   │   │   │       ├── custom-tasks.md
│   │   │   │       └── distributed-eval.md
│   │   │   ├── nemo-curator/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── deduplication.md
│   │   │   │       └── filtering.md
│   │   │   ├── saelens/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── README.md
│   │   │   │       ├── api.md
│   │   │   │       └── tutorials.md
│   │   │   └── weights-and-biases/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── artifacts.md
│   │   │           ├── integrations.md
│   │   │           └── sweeps.md
│   │   ├── huggingface-hub/
│   │   │   └── SKILL.md
│   │   ├── inference/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── gguf/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── guidance/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── backends.md
│   │   │   │       ├── constraints.md
│   │   │   │       └── examples.md
│   │   │   ├── instructor/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── examples.md
│   │   │   │       ├── providers.md
│   │   │   │       └── validation.md
│   │   │   ├── llama-cpp/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── optimization.md
│   │   │   │       ├── quantization.md
│   │   │   │       └── server.md
│   │   │   ├── obliteratus/
│   │   │   │   ├── SKILL.md
│   │   │   │   ├── references/
│   │   │   │   │   ├── analysis-modules.md
│   │   │   │   │   └── methods-guide.md
│   │   │   │   └── templates/
│   │   │   │       ├── abliteration-config.yaml
│   │   │   │       ├── analysis-study.yaml
│   │   │   │       └── batch-abliteration.yaml
│   │   │   ├── outlines/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── backends.md
│   │   │   │       ├── examples.md
│   │   │   │       └── json_generation.md
│   │   │   ├── tensorrt-llm/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── multi-gpu.md
│   │   │   │       ├── optimization.md
│   │   │   │       └── serving.md
│   │   │   └── vllm/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── optimization.md
│   │   │           ├── quantization.md
│   │   │           ├── server-deployment.md
│   │   │           └── troubleshooting.md
│   │   ├── models/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── audiocraft/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── clip/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       └── applications.md
│   │   │   ├── llava/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       └── training.md
│   │   │   ├── segment-anything/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── stable-diffusion/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   └── whisper/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           └── languages.md
│   │   ├── research/
│   │   │   ├── DESCRIPTION.md
│   │   │   └── dspy/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── examples.md
│   │   │           ├── modules.md
│   │   │           └── optimizers.md
│   │   ├── training/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── accelerate/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── custom-plugins.md
│   │   │   │       ├── megatron-integration.md
│   │   │   │       └── performance.md
│   │   │   ├── axolotl/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api.md
│   │   │   │       ├── dataset-formats.md
│   │   │   │       ├── index.md
│   │   │   │       └── other.md
│   │   │   ├── flash-attention/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── benchmarks.md
│   │   │   │       └── transformers-integration.md
│   │   │   ├── grpo-rl-training/
│   │   │   │   ├── README.md
│   │   │   │   ├── SKILL.md
│   │   │   │   └── templates/
│   │   │   │       └── basic_grpo_training.py
│   │   │   ├── hermes-atropos-environments/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── agentresult-fields.md
│   │   │   │       ├── atropos-base-env.md
│   │   │   │       └── usage-patterns.md
│   │   │   ├── peft/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── pytorch-fsdp/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── index.md
│   │   │   │       └── other.md
│   │   │   ├── pytorch-lightning/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── callbacks.md
│   │   │   │       ├── distributed.md
│   │   │   │       └── hyperparameter-tuning.md
│   │   │   ├── simpo/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── datasets.md
│   │   │   │       ├── hyperparameters.md
│   │   │   │       └── loss-functions.md
│   │   │   ├── slime/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api-reference.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── torchtitan/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── checkpoint.md
│   │   │   │       ├── custom-models.md
│   │   │   │       ├── float8.md
│   │   │   │       └── fsdp.md
│   │   │   ├── trl-fine-tuning/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── dpo-variants.md
│   │   │   │       ├── online-rl.md
│   │   │   │       ├── reward-modeling.md
│   │   │   │       └── sft-training.md
│   │   │   └── unsloth/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── index.md
│   │   │           ├── llms-full.md
│   │   │           ├── llms-txt.md
│   │   │           └── llms.md
│   │   └── vector-databases/
│   │       ├── DESCRIPTION.md
│   │       ├── chroma/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── integration.md
│   │       ├── faiss/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── index_types.md
│   │       ├── pinecone/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── deployment.md
│   │       └── qdrant/
│   │           ├── SKILL.md
│   │           └── references/
│   │               ├── advanced-usage.md
│   │               └── troubleshooting.md
│   ├── music-creation/
│   │   └── DESCRIPTION.md
│   ├── note-taking/
│   │   ├── DESCRIPTION.md
│   │   └── obsidian/
│   │       └── SKILL.md
│   ├── productivity/
│   │   ├── DESCRIPTION.md
│   │   ├── google-workspace/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   └── gmail-search-syntax.md
│   │   │   └── scripts/
│   │   │       ├── google_api.py
│   │   │       └── setup.py
│   │   ├── linear/
│   │   │   └── SKILL.md
│   │   ├── nano-pdf/
│   │   │   └── SKILL.md
│   │   ├── notion/
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       └── block-types.md
│   │   ├── ocr-and-documents/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       ├── extract_marker.py
│   │   │       └── extract_pymupdf.py
│   │   └── powerpoint/
│   │       ├── LICENSE.txt
│   │       ├── SKILL.md
│   │       ├── editing.md
│   │       ├── pptxgenjs.md
│   │       └── scripts/
│   │           ├── __init__.py
│   │           ├── add_slide.py
│   │           ├── clean.py
│   │           └── office/
│   │               ├── helpers/
│   │               │   ├── __init__.py
│   │               │   ├── merge_runs.py
│   │               │   └── simplify_redlines.py
│   │               ├── pack.py
│   │               └── schemas/
│   │                   ├── ISO-IEC29500-4_2016/
│   │                   │   ├── dml-chart.xsd
│   │                   │   ├── dml-chartDrawing.xsd
│   │                   │   ├── dml-diagram.xsd
│   │                   │   ├── dml-lockedCanvas.xsd
│   │                   │   ├── dml-main.xsd
│   │                   │   ├── dml-picture.xsd
│   │                   │   ├── dml-spreadsheetDrawing.xsd
│   │                   │   ├── dml-wordprocessingDrawing.xsd
│   │                   │   ├── pml.xsd
│   │                   │   ├── shared-additionalCharacteristics.xsd
│   │                   │   ├── shared-bibliography.xsd
│   │                   │   ├── shared-commonSimpleTypes.xsd
│   │                   │   ├── shared-customXmlDataProperties.xsd
│   │                   │   ├── shared-customXmlSchemaProperties.xsd
│   │                   │   ├── shared-documentPropertiesCustom.xsd
│   │                   │   ├── shared-documentPropertiesExtended.xsd
│   │                   │   ├── shared-documentPropertiesVariantTypes.xsd
│   │                   │   ├── shared-math.xsd
│   │                   │   ├── shared-relationshipReference.xsd
│   │                   │   ├── sml.xsd
│   │                   │   ├── vml-main.xsd
│   │                   │   ├── vml-officeDrawing.xsd
│   │                   │   ├── vml-presentationDrawing.xsd
│   │                   │   ├── vml-spreadsheetDrawing.xsd
│   │                   │   ├── vml-wordprocessingDrawing.xsd
│   │                   │   ├── wml.xsd
│   │                   │   └── xml.xsd
│   │                   ├── ecma/
│   │                   │   └── fourth-edition/
│   │                   │       ├── opc-contentTypes.xsd
│   │                   │       ├── opc-coreProperties.xsd
│   │                   │       ├── opc-digSig.xsd
│   │                   │       └── opc-relationships.xsd
│   │                   ├── mce/
│   │                   │   └── mc.xsd
│   │                   └── microsoft/
│   │                       ├── wml-2010.xsd
│   │                       ├── wml-2012.xsd
│   │                       ├── wml-2018.xsd
│   │                       ├── wml-cex-2018.xsd
│   │                       ├── wml-cid-2016.xsd
│   │                       ├── wml-sdtdatahash-2020.xsd
│   │                       └── wml-symex-2015.xsd
│   ├── research/
│   │   ├── DESCRIPTION.md
│   │   ├── arxiv/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── search_arxiv.py
│   │   ├── blogwatcher/
│   │   │   └── SKILL.md
│   │   ├── domain-intel/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── domain_intel.py
│   │   ├── duckduckgo-search/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── duckduckgo.sh
│   │   ├── ml-paper-writing/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   ├── checklists.md
│   │   │   │   ├── citation-workflow.md
│   │   │   │   ├── reviewer-guidelines.md
│   │   │   │   ├── sources.md
│   │   │   │   └── writing-guide.md
│   │   │   └── templates/
│   │   │       ├── README.md
│   │   │       ├── aaai2026/
│   │   │       │   ├── README.md
│   │   │       │   ├── aaai2026-unified-supp.tex
│   │   │       │   ├── aaai2026-unified-template.tex
│   │   │       │   ├── aaai2026.bib
│   │   │       │   ├── aaai2026.bst
│   │   │       │   └── aaai2026.sty
│   │   │       ├── acl/
│   │   │       │   ├── README.md
│   │   │       │   ├── acl.sty
│   │   │       │   ├── acl_latex.tex
│   │   │       │   ├── acl_lualatex.tex
│   │   │       │   ├── acl_natbib.bst
│   │   │       │   ├── anthology.bib.txt
│   │   │       │   ├── custom.bib
│   │   │       │   └── formatting.md
│   │   │       ├── colm2025/
│   │   │       │   ├── README.md
│   │   │       │   ├── colm2025_conference.bib
│   │   │       │   ├── colm2025_conference.bst
│   │   │       │   ├── colm2025_conference.sty
│   │   │       │   ├── colm2025_conference.tex
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── math_commands.tex
│   │   │       │   └── natbib.sty
│   │   │       ├── iclr2026/
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── iclr2026_conference.bib
│   │   │       │   ├── iclr2026_conference.bst
│   │   │       │   ├── iclr2026_conference.sty
│   │   │       │   ├── iclr2026_conference.tex
│   │   │       │   ├── math_commands.tex
│   │   │       │   └── natbib.sty
│   │   │       ├── icml2026/
│   │   │       │   ├── algorithm.sty
│   │   │       │   ├── algorithmic.sty
│   │   │       │   ├── example_paper.bib
│   │   │       │   ├── example_paper.tex
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── icml2026.bst
│   │   │       │   └── icml2026.sty
│   │   │       └── neurips2025/
│   │   │           ├── Makefile
│   │   │           ├── extra_pkgs.tex
│   │   │           ├── main.tex
│   │   │           └── neurips.sty
│   │   ├── parallel-cli/
│   │   │   └── SKILL.md
│   │   └── polymarket/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── api-endpoints.md
│   │       └── scripts/
│   │           └── polymarket.py
│   ├── smart-home/
│   │   ├── DESCRIPTION.md
│   │   └── openhue/
│   │       └── SKILL.md
│   ├── social-media/
│   │   ├── DESCRIPTION.md
│   │   └── xitter/
│   │       └── SKILL.md
│   └── software-development/
│       ├── code-review/
│       │   └── SKILL.md
│       ├── plan/
│       │   └── SKILL.md
│       ├── requesting-code-review/
│       │   └── SKILL.md
│       ├── subagent-driven-development/
│       │   └── SKILL.md
│       ├── systematic-debugging/
│       │   └── SKILL.md
│       ├── test-driven-development/
│       │   └── SKILL.md
│       └── writing-plans/
│           └── SKILL.md
├── tests/
│   ├── __init__.py
│   ├── acp/
│   │   ├── __init__.py
│   │   ├── test_auth.py
│   │   ├── test_events.py
│   │   ├── test_permissions.py
│   │   ├── test_server.py
│   │   ├── test_session.py
│   │   └── test_tools.py
│   ├── agent/
│   │   ├── __init__.py
│   │   ├── test_auxiliary_client.py
│   │   ├── test_context_compressor.py
│   │   ├── test_display_emoji.py
│   │   ├── test_model_metadata.py
│   │   ├── test_models_dev.py
│   │   ├── test_prompt_builder.py
│   │   ├── test_prompt_caching.py
│   │   ├── test_redact.py
│   │   ├── test_skill_commands.py
│   │   ├── test_smart_model_routing.py
│   │   ├── test_subagent_progress.py
│   │   ├── test_title_generator.py
│   │   └── test_usage_pricing.py
│   ├── conftest.py
│   ├── cron/
│   │   ├── __init__.py
│   │   ├── test_jobs.py
│   │   └── test_scheduler.py
│   ├── fakes/
│   │   ├── __init__.py
│   │   └── fake_ha_server.py
│   ├── gateway/
│   │   ├── __init__.py
│   │   ├── test_api_server.py
│   │   ├── test_approve_deny_commands.py
│   │   ├── test_async_memory_flush.py
│   │   ├── test_background_command.py
│   │   ├── test_background_process_notifications.py
│   │   ├── test_base_topic_sessions.py
│   │   ├── test_channel_directory.py
│   │   ├── test_config.py
│   │   ├── test_config_cwd_bridge.py
│   │   ├── test_delivery.py
│   │   ├── test_dingtalk.py
│   │   ├── test_discord_bot_filter.py
│   │   ├── test_discord_free_response.py
│   │   ├── test_discord_imports.py
│   │   ├── test_discord_media_metadata.py
│   │   ├── test_discord_opus.py
│   │   ├── test_discord_send.py
│   │   ├── test_discord_slash_commands.py
│   │   ├── test_discord_thread_persistence.py
│   │   ├── test_document_cache.py
│   │   ├── test_email.py
│   │   ├── test_extract_local_files.py
│   │   ├── test_gateway_shutdown.py
│   │   ├── test_homeassistant.py
│   │   ├── test_honcho_lifecycle.py
│   │   ├── test_hooks.py
│   │   ├── test_interrupt_key_match.py
│   │   ├── test_matrix.py
│   │   ├── test_mattermost.py
│   │   ├── test_media_extraction.py
│   │   ├── test_mirror.py
│   │   ├── test_pairing.py
│   │   ├── test_pii_redaction.py
│   │   ├── test_plan_command.py
│   │   ├── test_platform_base.py
│   │   ├── test_reasoning_command.py
│   │   ├── test_resume_command.py
│   │   ├── test_retry_replacement.py
│   │   ├── test_retry_response.py
│   │   ├── test_run_progress_topics.py
│   │   ├── test_runner_fatal_adapter.py
│   │   ├── test_runner_startup_failures.py
│   │   ├── test_send_image_file.py
│   │   ├── test_session.py
│   │   ├── test_session_env.py
│   │   ├── test_session_hygiene.py
│   │   ├── test_session_race_guard.py
│   │   ├── test_signal.py
│   │   ├── test_slack.py
│   │   ├── test_sms.py
│   │   ├── test_ssl_certs.py
│   │   ├── test_status.py
│   │   ├── test_status_command.py
│   │   ├── test_sticker_cache.py
│   │   ├── test_stt_config.py
│   │   ├── test_telegram_conflict.py
│   │   ├── test_telegram_documents.py
│   │   ├── test_telegram_format.py
│   │   ├── test_telegram_photo_interrupts.py
│   │   ├── test_telegram_text_batching.py
│   │   ├── test_title_command.py
│   │   ├── test_transcript_offset.py
│   │   ├── test_unauthorized_dm_behavior.py
│   │   ├── test_update_command.py
│   │   ├── test_voice_command.py
│   │   ├── test_webhook_adapter.py
│   │   ├── test_webhook_integration.py
│   │   ├── test_whatsapp_connect.py
│   │   └── test_whatsapp_reply_prefix.py
│   ├── hermes_cli/
│   │   ├── __init__.py
│   │   ├── test_banner.py
│   │   ├── test_banner_skills.py
│   │   ├── test_chat_skills_flag.py
│   │   ├── test_claw.py
│   │   ├── test_cmd_update.py
│   │   ├── test_coalesce_session_args.py
│   │   ├── test_commands.py
│   │   ├── test_config.py
│   │   ├── test_copilot_auth.py
│   │   ├── test_cron.py
│   │   ├── test_doctor.py
│   │   ├── test_env_loader.py
│   │   ├── test_gateway.py
│   │   ├── test_gateway_linger.py
│   │   ├── test_gateway_runtime_health.py
│   │   ├── test_gateway_service.py
│   │   ├── test_mcp_tools_config.py
│   │   ├── test_model_validation.py
│   │   ├── test_models.py
│   │   ├── test_path_completion.py
│   │   ├── test_placeholder_usage.py
│   │   ├── test_session_browse.py
│   │   ├── test_sessions_delete.py
│   │   ├── test_set_config_value.py
│   │   ├── test_setup.py
│   │   ├── test_setup_model_provider.py
│   │   ├── test_setup_noninteractive.py
│   │   ├── test_setup_openclaw_migration.py
│   │   ├── test_setup_prompt_menus.py
│   │   ├── test_skills_config.py
│   │   ├── test_skills_hub.py
│   │   ├── test_skills_install_flags.py
│   │   ├── test_skills_skip_confirm.py
│   │   ├── test_skills_subparser.py
│   │   ├── test_skin_engine.py
│   │   ├── test_status.py
│   │   ├── test_status_model_provider.py
│   │   ├── test_tools_config.py
│   │   ├── test_tools_disable_enable.py
│   │   ├── test_update_autostash.py
│   │   ├── test_update_check.py
│   │   └── test_update_gateway_restart.py
│   ├── honcho_integration/
│   │   ├── __init__.py
│   │   ├── test_async_memory.py
│   │   ├── test_cli.py
│   │   ├── test_client.py
│   │   └── test_session.py
│   ├── integration/
│   │   ├── __init__.py
│   │   ├── test_batch_runner.py
│   │   ├── test_checkpoint_resumption.py
│   │   ├── test_daytona_terminal.py
│   │   ├── test_ha_integration.py
│   │   ├── test_modal_terminal.py
│   │   ├── test_voice_channel_flow.py
│   │   └── test_web_tools.py
│   ├── run_interrupt_test.py
│   ├── skills/
│   │   ├── test_google_oauth_setup.py
│   │   ├── test_openclaw_migration.py
│   │   └── test_telephony_skill.py
│   ├── test_1630_context_overflow_loop.py
│   ├── test_413_compression.py
│   ├── test_860_dedup.py
│   ├── test_agent_guardrails.py
│   ├── test_agent_loop.py
│   ├── test_agent_loop_tool_calling.py
│   ├── test_agent_loop_vllm.py
│   ├── test_anthropic_adapter.py
│   ├── test_anthropic_error_handling.py
│   ├── test_anthropic_oauth_flow.py
│   ├── test_anthropic_provider_persistence.py
│   ├── test_api_key_providers.py
│   ├── test_atomic_json_write.py
│   ├── test_atomic_yaml_write.py
│   ├── test_auth_codex_provider.py
│   ├── test_auth_nous_provider.py
│   ├── test_auxiliary_config_bridge.py
│   ├── test_batch_runner_checkpoint.py
│   ├── test_cli_approval_ui.py
│   ├── test_cli_init.py
│   ├── test_cli_interrupt_subagent.py
│   ├── test_cli_loading_indicator.py
│   ├── test_cli_mcp_config_watch.py
│   ├── test_cli_model_command.py
│   ├── test_cli_new_session.py
│   ├── test_cli_plan_command.py
│   ├── test_cli_prefix_matching.py
│   ├── test_cli_preloaded_skills.py
│   ├── test_cli_provider_resolution.py
│   ├── test_cli_retry.py
│   ├── test_cli_secret_capture.py
│   ├── test_cli_skin_integration.py
│   ├── test_cli_status_bar.py
│   ├── test_cli_tools_command.py
│   ├── test_codex_execution_paths.py
│   ├── test_codex_models.py
│   ├── test_compression_boundary.py
│   ├── test_context_pressure.py
│   ├── test_context_token_tracking.py
│   ├── test_dict_tool_call_args.py
│   ├── test_display.py
│   ├── test_evidence_store.py
│   ├── test_external_credential_detection.py
│   ├── test_fallback_model.py
│   ├── test_file_permissions.py
│   ├── test_flush_memories_codex.py
│   ├── test_hermes_state.py
│   ├── test_honcho_client_config.py
│   ├── test_insights.py
│   ├── test_interactive_interrupt.py
│   ├── test_interrupt_propagation.py
│   ├── test_managed_server_tool_support.py
│   ├── test_minisweagent_path.py
│   ├── test_model_metadata_local_ctx.py
│   ├── test_model_provider_persistence.py
│   ├── test_model_tools.py
│   ├── test_model_tools_async_bridge.py
│   ├── test_openai_client_lifecycle.py
│   ├── test_personality_none.py
│   ├── test_plugins.py
│   ├── test_provider_parity.py
│   ├── test_quick_commands.py
│   ├── test_real_interrupt_subagent.py
│   ├── test_reasoning_command.py
│   ├── test_redirect_stdout_issue.py
│   ├── test_resume_display.py
│   ├── test_run_agent.py
│   ├── test_run_agent_codex_responses.py
│   ├── test_runtime_provider_resolution.py
│   ├── test_setup_model_selection.py
│   ├── test_sql_injection.py
│   ├── test_streaming.py
│   ├── test_timezone.py
│   ├── test_tool_call_parsers.py
│   ├── test_toolset_distributions.py
│   ├── test_toolsets.py
│   ├── test_trajectory_compressor.py
│   ├── test_worktree.py
│   ├── test_worktree_security.py
│   └── tools/
│       ├── __init__.py
│       ├── test_approval.py
│       ├── test_browser_cdp_override.py
│       ├── test_browser_cleanup.py
│       ├── test_browser_console.py
│       ├── test_checkpoint_manager.py
│       ├── test_clarify_tool.py
│       ├── test_clipboard.py
│       ├── test_code_execution.py
│       ├── test_command_guards.py
│       ├── test_cron_prompt_injection.py
│       ├── test_cronjob_tools.py
│       ├── test_daytona_environment.py
│       ├── test_debug_helpers.py
│       ├── test_delegate.py
│       ├── test_docker_environment.py
│       ├── test_docker_find.py
│       ├── test_file_operations.py
│       ├── test_file_tools.py
│       ├── test_file_tools_live.py
│       ├── test_file_write_safety.py
│       ├── test_force_dangerous_override.py
│       ├── test_fuzzy_match.py
│       ├── test_hidden_dir_filter.py
│       ├── test_homeassistant_tool.py
│       ├── test_honcho_tools.py
│       ├── test_interrupt.py
│       ├── test_local_env_blocklist.py
│       ├── test_local_persistent.py
│       ├── test_mcp_probe.py
│       ├── test_mcp_tool.py
│       ├── test_mcp_tool_issue_948.py
│       ├── test_memory_tool.py
│       ├── test_mixture_of_agents_tool.py
│       ├── test_modal_sandbox_fixes.py
│       ├── test_parse_env_var.py
│       ├── test_patch_parser.py
│       ├── test_process_registry.py
│       ├── test_read_loop_detection.py
│       ├── test_registry.py
│       ├── test_rl_training_tool.py
│       ├── test_search_hidden_dirs.py
│       ├── test_send_message_tool.py
│       ├── test_session_search.py
│       ├── test_singularity_preflight.py
│       ├── test_skill_manager_tool.py
│       ├── test_skill_view_path_check.py
│       ├── test_skill_view_traversal.py
│       ├── test_skills_guard.py
│       ├── test_skills_hub.py
│       ├── test_skills_hub_clawhub.py
│       ├── test_skills_sync.py
│       ├── test_skills_tool.py
│       ├── test_ssh_environment.py
│       ├── test_symlink_prefix_confusion.py
│       ├── test_terminal_disk_usage.py
│       ├── test_terminal_requirements.py
│       ├── test_terminal_tool_requirements.py
│       ├── test_tirith_security.py
│       ├── test_todo_tool.py
│       ├── test_transcription.py
│       ├── test_transcription_tools.py
│       ├── test_vision_tools.py
│       ├── test_voice_cli_integration.py
│       ├── test_voice_mode.py
│       ├── test_web_tools_config.py
│       ├── test_web_tools_tavily.py
│       ├── test_website_policy.py
│       ├── test_windows_compat.py
│       ├── test_write_deny.py
│       └── test_yolo_mode.py
├── tools/
│   ├── __init__.py
│   ├── approval.py
│   ├── browser_providers/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── browser_use.py
│   │   └── browserbase.py
│   ├── browser_tool.py
│   ├── checkpoint_manager.py
│   ├── clarify_tool.py
│   ├── code_execution_tool.py
│   ├── cronjob_tools.py
│   ├── debug_helpers.py
│   ├── delegate_tool.py
│   ├── environments/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── daytona.py
│   │   ├── docker.py
│   │   ├── local.py
│   │   ├── modal.py
│   │   ├── persistent_shell.py
│   │   ├── singularity.py
│   │   └── ssh.py
│   ├── file_operations.py
│   ├── file_tools.py
│   ├── fuzzy_match.py
│   ├── homeassistant_tool.py
│   ├── honcho_tools.py
│   ├── image_generation_tool.py
│   ├── interrupt.py
│   ├── mcp_tool.py
│   ├── memory_tool.py
│   ├── mixture_of_agents_tool.py
│   ├── neutts_samples/
│   │   └── jo.txt
│   ├── neutts_synth.py
│   ├── openrouter_client.py
│   ├── patch_parser.py
│   ├── process_registry.py
│   ├── registry.py
│   ├── rl_training_tool.py
│   ├── send_message_tool.py
│   ├── session_search_tool.py
│   ├── skill_manager_tool.py
│   ├── skills_guard.py
│   ├── skills_hub.py
│   ├── skills_sync.py
│   ├── skills_tool.py
│   ├── terminal_tool.py
│   ├── tirith_security.py
│   ├── todo_tool.py
│   ├── transcription_tools.py
│   ├── tts_tool.py
│   ├── vision_tools.py
│   ├── voice_mode.py
│   ├── web_tools.py
│   └── website_policy.py
├── toolset_distributions.py
├── toolsets.py
├── trajectory_compressor.py
├── utils.py
└── website/
    ├── .gitignore
    ├── README.md
    ├── docs/
    │   ├── developer-guide/
    │   │   ├── _category_.json
    │   │   ├── acp-internals.md
    │   │   ├── adding-providers.md
    │   │   ├── adding-tools.md
    │   │   ├── agent-loop.md
    │   │   ├── architecture.md
    │   │   ├── context-compression-and-caching.md
    │   │   ├── contributing.md
    │   │   ├── creating-skills.md
    │   │   ├── cron-internals.md
    │   │   ├── environments.md
    │   │   ├── gateway-internals.md
    │   │   ├── prompt-assembly.md
    │   │   ├── provider-runtime.md
    │   │   ├── session-storage.md
    │   │   ├── tools-runtime.md
    │   │   └── trajectory-format.md
    │   ├── getting-started/
    │   │   ├── _category_.json
    │   │   ├── installation.md
    │   │   ├── learning-path.md
    │   │   ├── quickstart.md
    │   │   └── updating.md
    │   ├── guides/
    │   │   ├── _category_.json
    │   │   ├── build-a-hermes-plugin.md
    │   │   ├── daily-briefing-bot.md
    │   │   ├── python-library.md
    │   │   ├── team-telegram-assistant.md
    │   │   ├── tips.md
    │   │   ├── use-mcp-with-hermes.md
    │   │   ├── use-soul-with-hermes.md
    │   │   └── use-voice-mode-with-hermes.md
    │   ├── index.md
    │   ├── reference/
    │   │   ├── _category_.json
    │   │   ├── cli-commands.md
    │   │   ├── environment-variables.md
    │   │   ├── faq.md
    │   │   ├── mcp-config-reference.md
    │   │   ├── optional-skills-catalog.md
    │   │   ├── skills-catalog.md
    │   │   ├── slash-commands.md
    │   │   ├── tools-reference.md
    │   │   └── toolsets-reference.md
    │   └── user-guide/
    │       ├── _category_.json
    │       ├── checkpoints-and-rollback.md
    │       ├── cli.md
    │       ├── configuration.md
    │       ├── features/
    │       │   ├── _category_.json
    │       │   ├── acp.md
    │       │   ├── api-server.md
    │       │   ├── batch-processing.md
    │       │   ├── browser.md
    │       │   ├── checkpoints.md
    │       │   ├── code-execution.md
    │       │   ├── context-files.md
    │       │   ├── cron.md
    │       │   ├── delegation.md
    │       │   ├── fallback-providers.md
    │       │   ├── honcho.md
    │       │   ├── hooks.md
    │       │   ├── image-generation.md
    │       │   ├── mcp.md
    │       │   ├── memory.md
    │       │   ├── personality.md
    │       │   ├── plugins.md
    │       │   ├── provider-routing.md
    │       │   ├── rl-training.md
    │       │   ├── skills.md
    │       │   ├── skins.md
    │       │   ├── tools.md
    │       │   ├── tts.md
    │       │   ├── vision.md
    │       │   └── voice-mode.md
    │       ├── git-worktrees.md
    │       ├── messaging/
    │       │   ├── _category_.json
    │       │   ├── dingtalk.md
    │       │   ├── discord.md
    │       │   ├── email.md
    │       │   ├── homeassistant.md
    │       │   ├── index.md
    │       │   ├── matrix.md
    │       │   ├── mattermost.md
    │       │   ├── open-webui.md
    │       │   ├── signal.md
    │       │   ├── slack.md
    │       │   ├── sms.md
    │       │   ├── telegram.md
    │       │   ├── webhooks.md
    │       │   └── whatsapp.md
    │       ├── security.md
    │       └── sessions.md
    ├── docusaurus.config.ts
    ├── package.json
    ├── sidebars.ts
    ├── src/
    │   └── css/
    │       └── custom.css
    ├── static/
    │   └── .nojekyll
    └── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "🐛 Bug Report"
description: Report a bug — something that's broken, crashes, or behaves incorrectly.
title: "[Bug]: "
labels: ["bug"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for reporting a bug! Please fill out the sections below so we can reproduce and fix it quickly.

        **Before submitting**, please:
        - [ ] Search [existing issues](https://github.com/NousResearch/hermes-agent/issues) to avoid duplicates
        - [ ] Update to the latest version (`hermes update`) and confirm the bug still exists

  - type: textarea
    id: description
    attributes:
      label: Bug Description
      description: A clear description of what's broken. Include error messages, tracebacks, or screenshots if relevant.
      placeholder: |
        What happened? What did you expect to happen instead?
    validations:
      required: true

  - type: textarea
    id: reproduction
    attributes:
      label: Steps to Reproduce
      description: Minimal steps to trigger the bug. The more specific, the faster we can fix it.
      placeholder: |
        1. Run `hermes chat`
        2. Send the message "..."
        3. Agent calls tool X
        4. Error appears: ...
    validations:
      required: true

  - type: textarea
    id: expected
    attributes:
      label: Expected Behavior
      description: What should have happened instead?
    validations:
      required: true

  - type: textarea
    id: actual
    attributes:
      label: Actual Behavior
      description: What actually happened? Include full error output if available.
    validations:
      required: true

  - type: dropdown
    id: component
    attributes:
      label: Affected Component
      description: Which part of Hermes is affected?
      multiple: true
      options:
        - CLI (interactive chat)
        - Gateway (Telegram/Discord/Slack/WhatsApp)
        - Setup / Installation
        - Tools (terminal, file ops, web, code execution, etc.)
        - Skills (skill loading, skill hub, skill guard)
        - Agent Core (conversation loop, context compression, memory)
        - Configuration (config.yaml, .env, hermes setup)
        - Other
    validations:
      required: true

  - type: dropdown
    id: platform
    attributes:
      label: Messaging Platform (if gateway-related)
      description: Which platform adapter is affected?
      multiple: true
      options:
        - N/A (CLI only)
        - Telegram
        - Discord
        - Slack
        - WhatsApp

  - type: input
    id: os
    attributes:
      label: Operating System
      description: e.g. Ubuntu 24.04, macOS 15.2, Windows 11
      placeholder: Ubuntu 24.04
    validations:
      required: true

  - type: input
    id: python-version
    attributes:
      label: Python Version
      description: Output of `python --version`
      placeholder: "3.11.9"
    validations:
      required: true

  - type: input
    id: hermes-version
    attributes:
      label: Hermes Version
      description: Output of `hermes version`
      placeholder: "2.1.0"
    validations:
      required: true

  - type: textarea
    id: logs
    attributes:
      label: Relevant Logs / Traceback
      description: Paste any error output, traceback, or log messages. This will be auto-formatted as code.
      render: shell

  - type: textarea
    id: root-cause
    attributes:
      label: Root Cause Analysis (optional)
      description: |
        If you've dug into the code and identified the root cause, share it here.
        Include file paths, line numbers, and code snippets if possible. This massively speeds up fixes.
      placeholder: |
        The bug is in `gateway/run.py` line 949. `len(history)` counts session_meta entries
        but `agent_messages` was built from filtered history...

  - type: textarea
    id: proposed-fix
    attributes:
      label: Proposed Fix (optional)
      description: If you have a fix in mind (or a PR ready), describe it here.
      placeholder: |
        Replace `.get()` with `.pop()` on line 289 of `gateway/platforms/base.py`
        to actually clear the pending message after retrieval.

  - type: checkboxes
    id: pr-ready
    attributes:
      label: Are you willing to submit a PR for this?
      options:
        - label: I'd like to fix this myself and submit a PR


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
  - name: 💬 Nous Research Discord
    url: https://discord.gg/NousResearch
    about: For quick questions, showcasing projects, sharing skills, and community chat.
  - name: 📖 Documentation
    url: https://github.com/NousResearch/hermes-agent/blob/main/README.md
    about: Check the README and docs before opening an issue.
  - name: 🤝 Contributing Guide
    url: https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md
    about: Read this before submitting a PR.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: "✨ Feature Request"
description: Suggest a new feature or improvement.
title: "[Feature]: "
labels: ["enhancement"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for the suggestion! Before submitting, please consider:

        - **Is this a new skill?** Most capabilities should be [skills, not tools](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#should-it-be-a-skill-or-a-tool). If it's a specialized integration (crypto, NFT, niche SaaS), it belongs on the Skills Hub, not bundled.
        - **Search [existing issues](https://github.com/NousResearch/hermes-agent/issues)** — someone may have already proposed this.

  - type: textarea
    id: problem
    attributes:
      label: Problem or Use Case
      description: What problem does this solve? What are you trying to do that you can't today?
      placeholder: |
        I'm trying to use Hermes with [provider/platform/workflow] but currently
        there's no way to...
    validations:
      required: true

  - type: textarea
    id: solution
    attributes:
      label: Proposed Solution
      description: How do you think this should work? Be as specific as you can — CLI flags, config options, UI behavior.
      placeholder: |
        Add a `--foo` flag to `hermes chat` that enables...
        Or: Add a config key `bar.baz` that controls...
    validations:
      required: true

  - type: textarea
    id: alternatives
    attributes:
      label: Alternatives Considered
      description: What other approaches did you consider? Why is the proposed solution better?

  - type: dropdown
    id: type
    attributes:
      label: Feature Type
      options:
        - New tool
        - New bundled skill
        - CLI improvement
        - Gateway / messaging improvement
        - Configuration option
        - Performance / reliability
        - Developer experience (tests, docs, CI)
        - Other
    validations:
      required: true

  - type: dropdown
    id: scope
    attributes:
      label: Scope
      description: How big is this change?
      options:
        - Small (single file, < 50 lines)
        - Medium (few files, < 300 lines)
        - Large (new module or significant refactor)

  - type: checkboxes
    id: pr-ready
    attributes:
      label: Contribution
      options:
        - label: I'd like to implement this myself and submit a PR


================================================
FILE: .github/ISSUE_TEMPLATE/setup_help.yml
================================================
name: "🔧 Setup / Installation Help"
description: Having trouble installing or configuring Hermes? Ask here.
title: "[Setup]: "
labels: ["setup"]
body:
  - type: markdown
    attributes:
      value: |
        Sorry you're having trouble! Please fill out the details below so we can help.

        **Quick checks first:**
        - Run `hermes doctor` and include the output below
        - Try `hermes update` to get the latest version
        - Check the [README troubleshooting section](https://github.com/NousResearch/hermes-agent#troubleshooting)
        - For general questions, consider the [Nous Research Discord](https://discord.gg/NousResearch) for faster help

  - type: textarea
    id: description
    attributes:
      label: What's Going Wrong?
      description: Describe what you're trying to do and where it fails.
      placeholder: |
        I ran `hermes setup` and selected Nous Portal, but when I try to
        start the gateway I get...
    validations:
      required: true

  - type: textarea
    id: steps
    attributes:
      label: Steps Taken
      description: What did you do? Include the exact commands you ran.
      placeholder: |
        1. Ran the install script: `curl -fsSL ... | bash`
        2. Ran `hermes setup` and chose "Quick setup"
        3. Selected OpenRouter, entered API key
        4. Ran `hermes chat` and got error...
    validations:
      required: true

  - type: dropdown
    id: install-method
    attributes:
      label: Installation Method
      options:
        - Install script (curl | bash)
        - Manual clone + pip/uv install
        - PowerShell installer (Windows)
        - Docker
        - Other
    validations:
      required: true

  - type: input
    id: os
    attributes:
      label: Operating System
      placeholder: Ubuntu 24.04 / macOS 15.2 / Windows 11
    validations:
      required: true

  - type: input
    id: python-version
    attributes:
      label: Python Version
      description: Output of `python --version` (or `python3 --version`)
      placeholder: "3.11.9"

  - type: input
    id: hermes-version
    attributes:
      label: Hermes Version
      description: Output of `hermes version` (if install got that far)
      placeholder: "2.1.0"

  - type: textarea
    id: doctor-output
    attributes:
      label: Output of `hermes doctor`
      description: Run `hermes doctor` and paste the full output. This will be auto-formatted.
      render: shell

  - type: textarea
    id: error-output
    attributes:
      label: Full Error Output
      description: Paste the complete error message or traceback. This will be auto-formatted.
      render: shell
    validations:
      required: true

  - type: textarea
    id: tried
    attributes:
      label: What I've Already Tried
      description: List any fixes or workarounds you've already attempted.
      placeholder: |
        - Ran `hermes update`
        - Tried reinstalling with `pip install -e ".[all]"`
        - Checked that OPENROUTER_API_KEY is set in ~/.hermes/.env


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## What does this PR do?

<!-- Describe the change clearly. What problem does it solve? Why is this approach the right one? -->



## Related Issue

<!-- Link the issue this PR addresses. If no issue exists, consider creating one first. -->

Fixes #

## Type of Change

<!-- Check the one that applies. -->

- [ ] 🐛 Bug fix (non-breaking change that fixes an issue)
- [ ] ✨ New feature (non-breaking change that adds functionality)
- [ ] 🔒 Security fix
- [ ] 📝 Documentation update
- [ ] ✅ Tests (adding or improving test coverage)
- [ ] ♻️ Refactor (no behavior change)
- [ ] 🎯 New skill (bundled or hub)

## Changes Made

<!-- List the specific changes. Include file paths for code changes. -->

- 

## How to Test

<!-- Steps to verify this change works. For bugs: reproduction steps + proof that the fix works. -->

1. 
2. 
3. 

## Checklist

<!-- Complete these before requesting review. -->

### Code

- [ ] I've read the [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md)
- [ ] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`fix(scope):`, `feat(scope):`, etc.)
- [ ] I searched for [existing PRs](https://github.com/NousResearch/hermes-agent/pulls) to make sure this isn't a duplicate
- [ ] My PR contains **only** changes related to this fix/feature (no unrelated commits)
- [ ] I've run `pytest tests/ -q` and all tests pass
- [ ] I've added tests for my changes (required for bug fixes, strongly encouraged for features)
- [ ] I've tested on my platform: <!-- e.g. Ubuntu 24.04, macOS 15.2, Windows 11 -->

### Documentation & Housekeeping

<!-- Check all that apply. It's OK to check "N/A" if a category doesn't apply to your change. -->

- [ ] I've updated relevant documentation (README, `docs/`, docstrings) — or N/A
- [ ] I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A
- [ ] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A
- [ ] I've considered cross-platform impact (Windows, macOS) per the [compatibility guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-compatibility) — or N/A
- [ ] I've updated tool descriptions/schemas if I changed tool behavior — or N/A

## For New Skills

<!-- Only fill this out if you're adding a skill. Delete this section otherwise. -->

- [ ] This skill is **broadly useful** to most users (if bundled) — see [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#should-the-skill-be-bundled)
- [ ] SKILL.md follows the [standard format](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#skillmd-format) (frontmatter, trigger conditions, steps, pitfalls)
- [ ] No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools)
- [ ] I've tested the skill end-to-end: `hermes --toolsets skills -q "Use the X skill to do Y"`

## Screenshots / Logs

<!-- If applicable, add screenshots or log output showing the fix/feature in action. -->



================================================
FILE: .github/workflows/deploy-site.yml
================================================
name: Deploy Site

on:
  push:
    branches: [main]
    paths:
      - 'website/**'
      - 'landingpage/**'
      - '.github/workflows/deploy-site.yml'
  workflow_dispatch:

permissions:
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deploy.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: website/package-lock.json

      - name: Install dependencies
        run: npm ci
        working-directory: website

      - name: Build Docusaurus
        run: npm run build
        working-directory: website

      - name: Stage deployment
        run: |
          mkdir -p _site/docs
          # Landing page at root
          cp -r landingpage/* _site/
          # Docusaurus at /docs/
          cp -r website/build/* _site/docs/
          # CNAME so GitHub Pages keeps the custom domain between deploys
          echo "hermes-agent.nousresearch.com" > _site/CNAME

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: _site

      - name: Deploy to GitHub Pages
        id: deploy
        uses: actions/deploy-pages@v4


================================================
FILE: .github/workflows/docs-site-checks.yml
================================================
name: Docs Site Checks

on:
  pull_request:
    paths:
      - 'website/**'
      - '.github/workflows/docs-site-checks.yml'
  workflow_dispatch:

jobs:
  docs-site-checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: website/package-lock.json

      - name: Install website dependencies
        run: npm ci
        working-directory: website

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install ascii-guard
        run: python -m pip install ascii-guard

      - name: Lint docs diagrams
        run: npm run lint:diagrams
        working-directory: website

      - name: Build Docusaurus
        run: npm run build
        working-directory: website


================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Cancel in-progress runs for the same PR/branch
concurrency:
  group: tests-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v5

      - name: Set up Python 3.11
        run: uv python install 3.11

      - name: Install dependencies
        run: |
          uv venv .venv --python 3.11
          source .venv/bin/activate
          uv pip install -e ".[all,dev]"

      - name: Run tests
        run: |
          source .venv/bin/activate
          python -m pytest tests/ -q --ignore=tests/integration --tb=short -n auto
        env:
          # Ensure tests don't accidentally call real APIs
          OPENROUTER_API_KEY: ""
          OPENAI_API_KEY: ""
          NOUS_API_KEY: ""


================================================
FILE: .gitignore
================================================
/venv/
/_pycache/
*.pyc*
__pycache__/
.venv/
.vscode/
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.development
.env.test
export*
__pycache__/model_tools.cpython-310.pyc
__pycache__/web_tools.cpython-310.pyc
logs/
data/
.pytest_cache/
tmp/
temp_vision_images/
hermes-*/*
examples/
tests/quick_test_dataset.jsonl
tests/sample_dataset.jsonl
run_datagen_kimik2-thinking.sh
run_datagen_megascience_glm4-6.sh
run_datagen_sonnet.sh
source-data/*
run_datagen_megascience_glm4-6.sh
data/*
node_modules/
browser-use/
agent-browser/
# Private keys
*.ppk
*.pem
privvy*
images/
__pycache__/
hermes_agent.egg-info/
wandb/
testlogs

# CLI config (may contain sensitive SSH paths)
cli-config.yaml

# Skills Hub state (lives in ~/.hermes/skills/.hub/ at runtime, but just in case)
skills/.hub/
ignored/
.worktrees/
environments/benchmarks/evals/

# Release script temp files
.release_notes.md


================================================
FILE: .gitmodules
================================================
[submodule "mini-swe-agent"]
	path = mini-swe-agent
	url = https://github.com/SWE-agent/mini-swe-agent
[submodule "tinker-atropos"]
	path = tinker-atropos
	url = https://github.com/nousresearch/tinker-atropos


================================================
FILE: .plans/openai-api-server.md
================================================
# OpenAI-Compatible API Server for Hermes Agent

## Motivation

Every major chat frontend (Open WebUI 126k★, LobeChat 73k★, LibreChat 34k★,
AnythingLLM 56k★, NextChat 87k★, ChatBox 39k★, Jan 26k★, HF Chat-UI 8k★,
big-AGI 7k★) connects to backends via the OpenAI-compatible REST API with
SSE streaming. By exposing this endpoint, hermes-agent becomes instantly
usable as a backend for all of them — no custom adapters needed.

## What It Enables

```
┌──────────────────┐
│  Open WebUI      │──┐
│  LobeChat        │  │    POST /v1/chat/completions
│  LibreChat       │  ├──► Authorization: Bearer <key>     ┌─────────────────┐
│  AnythingLLM     │  │    {"messages": [...]}             │  hermes-agent   │
│  NextChat        │  │                                    │  gateway        │
│  Any OAI client  │──┘    ◄── SSE streaming response      │  (API server)   │
└──────────────────┘                                        └─────────────────┘
```

A user would:
1. Set `API_SERVER_ENABLED=true` in `~/.hermes/.env`
2. Run `hermes gateway` (API server starts alongside Telegram/Discord/etc.)
3. Point Open WebUI (or any frontend) at `http://localhost:8642/v1`
4. Chat with hermes-agent through any OpenAI-compatible UI

## Endpoints

| Method | Path | Purpose |
|--------|------|---------|
| POST | `/v1/chat/completions` | Chat with the agent (streaming + non-streaming) |
| GET | `/v1/models` | List available "models" (returns hermes-agent as a model) |
| GET | `/health` | Health check |

## Architecture

### Option A: Gateway Platform Adapter (recommended)

Create `gateway/platforms/api_server.py` as a new platform adapter that
extends `BasePlatformAdapter`. This is the cleanest approach because:

- Reuses all gateway infrastructure (session management, auth, context building)
- Runs in the same async loop as other adapters
- Gets message handling, interrupt support, and session persistence for free
- Follows the established pattern (like Telegram, Discord, etc.)
- Uses `aiohttp.web` (already a dependency) for the HTTP server

The adapter would start an `aiohttp.web.Application` server in `connect()`
and route incoming HTTP requests through the standard `handle_message()` pipeline.

### Option B: Standalone Component

A separate HTTP server class in `gateway/api_server.py` that creates its own
AIAgent instances directly. Simpler but duplicates session/auth logic.

**Recommendation: Option A** — fits the existing architecture, less code to
maintain, gets all gateway features for free.

## Request/Response Format

### Chat Completions (non-streaming)

```
POST /v1/chat/completions
Authorization: Bearer hermes-api-key-here
Content-Type: application/json

{
  "model": "hermes-agent",
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What files are in the current directory?"}
  ],
  "stream": false,
  "temperature": 0.7
}
```

Response:
```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1710000000,
  "model": "hermes-agent",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Here are the files in the current directory:\n..."
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 50,
    "completion_tokens": 200,
    "total_tokens": 250
  }
}
```

### Chat Completions (streaming)

Same request with `"stream": true`. Response is SSE:

```
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Here "},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"are "},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]
```

### Models List

```
GET /v1/models
Authorization: Bearer hermes-api-key-here
```

Response:
```json
{
  "object": "list",
  "data": [{
    "id": "hermes-agent",
    "object": "model",
    "created": 1710000000,
    "owned_by": "hermes-agent"
  }]
}
```

## Key Design Decisions

### 1. Session Management

The OpenAI API is stateless — each request includes the full conversation.
But hermes-agent sessions have persistent state (memory, skills, tool context).

**Approach: Hybrid**
- Default: Stateless. Each request is independent. The `messages` array IS
  the conversation. No session persistence between requests.
- Opt-in persistent sessions via `X-Session-ID` header. When provided, the
  server maintains session state across requests (conversation history,
  memory context, tool state). This enables richer agent behavior.
- The session ID also enables interrupt support — a subsequent request with
  the same session ID while one is running triggers an interrupt.

### 2. Streaming

The agent's `run_conversation()` is synchronous and returns the full response.
For real SSE streaming, we need to emit chunks as they're generated.

**Phase 1 (MVP):** Run agent in a thread, return the complete response as
a single SSE chunk + `[DONE]`. This works with all frontends — they just see
a fast single-chunk response. Not true streaming but functional.

**Phase 2:** Add a response callback to AIAgent that emits text chunks as the
LLM generates them. The API server captures these via a queue and streams them
as SSE events. This gives real token-by-token streaming.

**Phase 3:** Stream tool execution progress too — emit tool call/result events
as the agent works, giving frontends visibility into what the agent is doing.

### 3. Tool Transparency

Two modes:
- **Opaque (default):** Frontends see only the final response. Tool calls
  happen server-side and are invisible. Best for general-purpose UIs.
- **Transparent (opt-in via header):** Tool calls are emitted as OpenAI-format
  tool_call/tool_result messages in the stream. Useful for agent-aware frontends.

### 4. Authentication

- Bearer token via `Authorization: Bearer <key>` header
- Token configured via `API_SERVER_KEY` env var
- Optional: allow unauthenticated local-only access (127.0.0.1 bind)
- Follows the same pattern as other platform adapters

### 5. Model Mapping

Frontends send `"model": "hermes-agent"` (or whatever). The actual LLM model
used is configured server-side in config.yaml. The API server maps any
requested model name to the configured hermes-agent model.

Optionally, allow model passthrough: if the frontend sends
`"model": "anthropic/claude-sonnet-4"`, the agent uses that model. Controlled
by a config flag.

## Configuration

```yaml
# In config.yaml
api_server:
  enabled: true
  port: 8642
  host: "127.0.0.1"        # localhost only by default
  key: "your-secret-key"   # or via API_SERVER_KEY env var
  allow_model_override: false  # let clients choose the model
  max_concurrent: 5         # max simultaneous requests
```

Environment variables:
```bash
API_SERVER_ENABLED=true
API_SERVER_PORT=8642
API_SERVER_HOST=127.0.0.1
API_SERVER_KEY=your-secret-key
```

## Implementation Plan

### Phase 1: MVP (non-streaming) — PR

1. `gateway/platforms/api_server.py` — new adapter
   - aiohttp.web server with endpoints:
     - `POST /v1/chat/completions` — Chat Completions API (universal compat)
     - `POST /v1/responses` — Responses API (server-side state, tool preservation)
     - `GET /v1/models` — list available models
     - `GET /health` — health check
   - Bearer token auth middleware
   - Non-streaming responses (run agent, return full result)
   - Chat Completions: stateless, messages array is the conversation
   - Responses API: server-side conversation storage via previous_response_id
     - Store full internal conversation (including tool calls) keyed by response ID
     - On subsequent requests, reconstruct full context from stored chain
   - Frontend system prompt layered on top of hermes-agent's core prompt

2. `gateway/config.py` — add `Platform.API_SERVER` enum + config

3. `gateway/run.py` — register adapter in `_create_adapter()`

4. Tests in `tests/gateway/test_api_server.py`

### Phase 2: SSE Streaming

1. Add response streaming to both endpoints
   - Chat Completions: `choices[0].delta.content` SSE format
   - Responses API: semantic events (response.output_text.delta, etc.)
   - Run agent in thread, collect output via callback queue
   - Handle client disconnect (cancel agent)

2. Add `stream_callback` parameter to `AIAgent.run_conversation()`

### Phase 3: Enhanced Features

1. Tool call transparency mode (opt-in)
2. Model passthrough/override
3. Concurrent request limiting
4. Usage tracking / rate limiting
5. CORS headers for browser-based frontends
6. GET /v1/responses/{id} — retrieve stored response
7. DELETE /v1/responses/{id} — delete stored response

## Files Changed

| File | Change |
|------|--------|
| `gateway/platforms/api_server.py` | NEW — main adapter (~300 lines) |
| `gateway/config.py` | Add Platform.API_SERVER + config (~20 lines) |
| `gateway/run.py` | Register adapter in _create_adapter() (~10 lines) |
| `tests/gateway/test_api_server.py` | NEW — tests (~200 lines) |
| `cli-config.yaml.example` | Add api_server section |
| `README.md` | Mention API server in platform list |

## Compatibility Matrix

Once implemented, hermes-agent works as a drop-in backend for:

| Frontend | Stars | How to Connect |
|----------|-------|---------------|
| Open WebUI | 126k | Settings → Connections → Add OpenAI API, URL: `http://localhost:8642/v1` |
| NextChat | 87k | BASE_URL env var |
| LobeChat | 73k | Custom provider endpoint |
| AnythingLLM | 56k | LLM Provider → Generic OpenAI |
| Oobabooga | 42k | Already a backend, not a frontend |
| ChatBox | 39k | API Host setting |
| LibreChat | 34k | librechat.yaml custom endpoint |
| Chatbot UI | 29k | Custom API endpoint |
| Jan | 26k | Remote model config |
| AionUI | 18k | Custom API endpoint |
| HF Chat-UI | 8k | OPENAI_BASE_URL env var |
| big-AGI | 7k | Custom endpoint |


================================================
FILE: .plans/streaming-support.md
================================================
# Streaming LLM Response Support for Hermes Agent

## Overview

Add token-by-token streaming of LLM responses across all platforms. When enabled,
users see the response typing out live instead of waiting for the full generation.
Streaming is opt-in via config, defaults to off, and all existing non-streaming
code paths remain intact as the default.

## Design Principles

1. **Feature-flagged**: `streaming.enabled: true` in config.yaml. Off by default.
   When off, all existing code paths are unchanged — zero risk to current behavior.
2. **Callback-based**: A simple `stream_callback(text_delta: str)` function injected
   into AIAgent. The agent doesn't know or care what the consumer does with tokens.
3. **Graceful degradation**: If the provider doesn't support streaming, or streaming
   fails for any reason, silently fall back to the non-streaming path.
4. **Platform-agnostic core**: The streaming mechanism in AIAgent works the same
   regardless of whether the consumer is CLI, Telegram, Discord, or the API server.

---

## Architecture

```
                              stream_callback(delta)
                                    │
  ┌─────────────┐    ┌─────────────▼──────────────┐
  │  LLM API    │    │      queue.Queue()          │
  │  (stream)   │───►│  thread-safe bridge between │
  │             │    │  agent thread & consumer    │
  └─────────────┘    └─────────────┬──────────────┘
                                   │
                    ┌──────────────┼──────────────┐
                    │              │              │
              ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
              │    CLI     │ │  Gateway  │ │ API Server│
              │ print to   │ │ edit msg  │ │ SSE event │
              │ terminal   │ │ on Tg/Dc  │ │ to client │
              └───────────┘ └───────────┘ └───────────┘
```

The agent runs in a thread. The callback puts tokens into a thread-safe queue.
Each consumer reads the queue in its own context (async task, main thread, etc.).

---

## Configuration

### config.yaml

```yaml
streaming:
  enabled: false          # Master switch. Default off.
  # Per-platform overrides (optional):
  # cli: true             # Override for CLI only
  # telegram: true        # Override for Telegram only
  # discord: false        # Keep Discord non-streaming
  # api_server: true      # Override for API server
```

### Environment variables

```
HERMES_STREAMING_ENABLED=true    # Master switch via env
```

### How the flag is read

- **CLI**: `load_cli_config()` reads `streaming.enabled`, sets env var. AIAgent
  checks at init time.
- **Gateway**: `_run_agent()` reads config, decides whether to pass
  `stream_callback` to the AIAgent constructor.
- **API server**: For Chat Completions `stream=true` requests, always uses streaming
  regardless of config (the client is explicitly requesting it). For non-stream
  requests, uses config.

### Precedence

1. API server: client's `stream` field overrides everything
2. Per-platform config override (e.g., `streaming.telegram: true`)
3. Master `streaming.enabled` flag
4. Default: off

---

## Implementation Plan

### Phase 1: Core streaming infrastructure in AIAgent

**File: run_agent.py**

#### 1a. Add stream_callback parameter to __init__ (~5 lines)

```python
def __init__(self, ..., stream_callback: callable = None, ...):
    self.stream_callback = stream_callback
```

No other init changes. The callback is optional — when None, everything
works exactly as before.

#### 1b. Add _run_streaming_chat_completion() method (~65 lines)

New method for Chat Completions API streaming:

```python
def _run_streaming_chat_completion(self, api_kwargs: dict):
    """Stream a chat completion, emitting text tokens via stream_callback.
    
    Returns a fake response object compatible with the non-streaming code path.
    Falls back to non-streaming on any error.
    """
    stream_kwargs = dict(api_kwargs)
    stream_kwargs["stream"] = True
    stream_kwargs["stream_options"] = {"include_usage": True}
    
    accumulated_content = []
    accumulated_tool_calls = {}  # index -> {id, name, arguments}
    final_usage = None
    
    try:
        stream = self.client.chat.completions.create(**stream_kwargs)
        
        for chunk in stream:
            if not chunk.choices:
                # Usage-only chunk (final)
                if chunk.usage:
                    final_usage = chunk.usage
                continue
            
            delta = chunk.choices[0].delta
            
            # Text content — emit via callback
            if delta.content:
                accumulated_content.append(delta.content)
                if self.stream_callback:
                    try:
                        self.stream_callback(delta.content)
                    except Exception:
                        pass
            
            # Tool call deltas — accumulate silently
            if delta.tool_calls:
                for tc_delta in delta.tool_calls:
                    idx = tc_delta.index
                    if idx not in accumulated_tool_calls:
                        accumulated_tool_calls[idx] = {
                            "id": tc_delta.id or "",
                            "name": "", "arguments": ""
                        }
                    if tc_delta.function:
                        if tc_delta.function.name:
                            accumulated_tool_calls[idx]["name"] = tc_delta.function.name
                        if tc_delta.function.arguments:
                            accumulated_tool_calls[idx]["arguments"] += tc_delta.function.arguments
        
        # Build fake response compatible with existing code
        tool_calls = []
        for idx in sorted(accumulated_tool_calls):
            tc = accumulated_tool_calls[idx]
            if tc["name"]:
                tool_calls.append(SimpleNamespace(
                    id=tc["id"], type="function",
                    function=SimpleNamespace(name=tc["name"], arguments=tc["arguments"]),
                ))
        
        return SimpleNamespace(
            choices=[SimpleNamespace(
                message=SimpleNamespace(
                    content="".join(accumulated_content) or "",
                    tool_calls=tool_calls or None,
                    role="assistant",
                ),
                finish_reason="tool_calls" if tool_calls else "stop",
            )],
            usage=final_usage,
            model=self.model,
        )
    
    except Exception as e:
        logger.debug("Streaming failed, falling back to non-streaming: %s", e)
        return self.client.chat.completions.create(**api_kwargs)
```

#### 1c. Modify _run_codex_stream() for Responses API (~10 lines)

The method already iterates the stream. Add callback emission:

```python
def _run_codex_stream(self, api_kwargs: dict):
    with self.client.responses.stream(**api_kwargs) as stream:
        for event in stream:
            # Emit text deltas if streaming callback is set
            if self.stream_callback and hasattr(event, 'type'):
                if event.type == 'response.output_text.delta':
                    try:
                        self.stream_callback(event.delta)
                    except Exception:
                        pass
        return stream.get_final_response()
```

#### 1d. Modify _interruptible_api_call() (~5 lines)

Add the streaming branch:

```python
def _call():
    try:
        if self.api_mode == "codex_responses":
            result["response"] = self._run_codex_stream(api_kwargs)
        elif self.stream_callback is not None:
            result["response"] = self._run_streaming_chat_completion(api_kwargs)
        else:
            result["response"] = self.client.chat.completions.create(**api_kwargs)
    except Exception as e:
        result["error"] = e
```

#### 1e. Signal end-of-stream to consumers (~5 lines)

After the API call returns, signal the callback that streaming is done
so consumers can finalize (remove cursor, close SSE, etc.):

```python
# In run_conversation(), after _interruptible_api_call returns:
if self.stream_callback:
    try:
        self.stream_callback(None)  # None = end of stream signal
    except Exception:
        pass
```

Consumers check: `if delta is None: finalize()`

**Tests for Phase 1:** (~150 lines)
- Test _run_streaming_chat_completion with mocked stream
- Test fallback to non-streaming on error
- Test tool_call accumulation during streaming
- Test stream_callback receives correct deltas
- Test None signal at end of stream
- Test streaming disabled when callback is None

---

### Phase 2: Gateway consumers (Telegram, Discord, etc.)

**File: gateway/run.py**

#### 2a. Read streaming config (~15 lines)

In `_run_agent()`, before creating the AIAgent:

```python
# Read streaming config
_streaming_enabled = False
try:
    # Check per-platform override first
    platform_key = source.platform.value if source.platform else ""
    _stream_cfg = {}  # loaded from config.yaml streaming section
    if _stream_cfg.get(platform_key) is not None:
        _streaming_enabled = bool(_stream_cfg[platform_key])
    else:
        _streaming_enabled = bool(_stream_cfg.get("enabled", False))
except Exception:
    pass
# Env var override
if os.getenv("HERMES_STREAMING_ENABLED", "").lower() in ("true", "1", "yes"):
    _streaming_enabled = True
```

#### 2b. Set up queue + callback (~15 lines)

```python
_stream_q = None
_stream_done = None
_stream_msg_id = [None]  # mutable ref for the async task

if _streaming_enabled:
    import queue as _q
    _stream_q = _q.Queue()
    _stream_done = threading.Event()
    
    def _on_token(delta):
        if delta is None:
            _stream_done.set()
        else:
            _stream_q.put(delta)
```

Pass `stream_callback=_on_token` to the AIAgent constructor.

#### 2c. Telegram/Discord stream preview task (~50 lines)

```python
async def stream_preview():
    """Progressively edit a message with streaming tokens."""
    if not _stream_q:
        return
    adapter = self.adapters.get(source.platform)
    if not adapter:
        return
    
    accumulated = []
    token_count = 0
    last_edit = 0.0
    MIN_TOKENS = 20          # Don't show until enough context
    EDIT_INTERVAL = 1.5      # Respect Telegram rate limits
    
    try:
        while not _stream_done.is_set():
            try:
                chunk = _stream_q.get(timeout=0.1)
                accumulated.append(chunk)
                token_count += 1
            except queue.Empty:
                continue
            
            now = time.monotonic()
            if token_count >= MIN_TOKENS and (now - last_edit) >= EDIT_INTERVAL:
                preview = "".join(accumulated) + " ▌"
                if _stream_msg_id[0] is None:
                    r = await adapter.send(
                        chat_id=source.chat_id,
                        content=preview,
                        metadata=_thread_metadata,
                    )
                    if r.success and r.message_id:
                        _stream_msg_id[0] = r.message_id
                else:
                    await adapter.edit_message(
                        chat_id=source.chat_id,
                        message_id=_stream_msg_id[0],
                        content=preview,
                    )
                last_edit = now
        
        # Drain remaining tokens
        while not _stream_q.empty():
            accumulated.append(_stream_q.get_nowait())
        
        # Final edit — remove cursor, show complete text
        if _stream_msg_id[0] and accumulated:
            await adapter.edit_message(
                chat_id=source.chat_id,
                message_id=_stream_msg_id[0],
                content="".join(accumulated),
            )
    
    except asyncio.CancelledError:
        # Clean up on cancel
        if _stream_msg_id[0] and accumulated:
            try:
                await adapter.edit_message(
                    chat_id=source.chat_id,
                    message_id=_stream_msg_id[0],
                    content="".join(accumulated),
                )
            except Exception:
                pass
    except Exception as e:
        logger.debug("stream_preview error: %s", e)
```

#### 2d. Skip final send if already streamed (~10 lines)

In `_process_message_background()` (base.py), after getting the response,
if streaming was active and `_stream_msg_id[0]` is set, the final response
was already delivered via progressive edits. Skip the normal `self.send()`
call to avoid duplicating the message.

This is the most delicate integration point — we need to communicate from
the gateway's `_run_agent` back to the base adapter's response sender that
the response was already delivered. Options:

- **Option A**: Return a special marker in the result dict:
  `result["_streamed_msg_id"] = _stream_msg_id[0]`
  The base adapter checks this and skips `send()`.
  
- **Option B**: Edit the already-sent message with the final response
  (which may differ slightly from accumulated tokens due to think-block
  stripping, etc.) and don't send a new one.

- **Option C**: The stream preview task handles the FULL final response
  (including any post-processing), and the handler returns None to skip
  the normal send path.

Recommended: **Option A** — cleanest separation. The result dict already
carries metadata; adding one more field is low-risk.

**Platform-specific considerations:**

| Platform | Edit support | Rate limits | Streaming approach |
|----------|-------------|-------------|-------------------|
| Telegram | ✅ edit_message_text | ~20 edits/min | Edit every 1.5s |
| Discord | ✅ message.edit | 5 edits/5s per message | Edit every 1.2s |
| Slack | ✅ chat.update | Tier 3 (~50/min) | Edit every 1.5s |
| WhatsApp | ❌ no edit support | N/A | Skip streaming, use normal path |
| HomeAssistant | ❌ no edit | N/A | Skip streaming |
| API Server | ✅ SSE native | No limit | Real SSE events |

WhatsApp and HomeAssistant fall back to non-streaming automatically because
they don't support message editing.

**Tests for Phase 2:** (~100 lines)
- Test stream_preview sends/edits correctly
- Test skip-final-send when streaming delivered
- Test WhatsApp/HA graceful fallback
- Test streaming disabled per-platform config
- Test thread_id metadata forwarded in stream messages

---

### Phase 3: CLI streaming

**File: cli.py**

#### 3a. Set up callback in the CLI chat loop (~20 lines)

In `_chat_once()` or wherever the agent is invoked:

```python
if streaming_enabled:
    _stream_q = queue.Queue()
    _stream_done = threading.Event()
    
    def _cli_stream_callback(delta):
        if delta is None:
            _stream_done.set()
        else:
            _stream_q.put(delta)
    
    agent.stream_callback = _cli_stream_callback
```

#### 3b. Token display thread/task (~30 lines)

Start a thread that reads the queue and prints tokens:

```python
def _stream_display():
    """Print tokens to terminal as they arrive."""
    first_token = True
    while not _stream_done.is_set():
        try:
            delta = _stream_q.get(timeout=0.1)
        except queue.Empty:
            continue
        if first_token:
            # Print response box top border
            _cprint(f"\n{top}")
            first_token = False
        sys.stdout.write(delta)
        sys.stdout.flush()
    # Drain remaining
    while not _stream_q.empty():
        sys.stdout.write(_stream_q.get_nowait())
    sys.stdout.flush()
    # Print bottom border
    _cprint(f"\n\n{bot}")
```

**Integration challenge: prompt_toolkit**

The CLI uses prompt_toolkit which controls the terminal. Writing directly
to stdout while prompt_toolkit is active can cause display corruption.
The existing KawaiiSpinner already solves this by using prompt_toolkit's
`patch_stdout` context. The streaming display would need to do the same.

Alternative: use `_cprint()` for each token chunk (routes through
prompt_toolkit's renderer). But this might be slow for individual tokens.

Recommended approach: accumulate tokens in small batches (e.g., every 50ms)
and `_cprint()` the batch. This balances display responsiveness with
prompt_toolkit compatibility.

**Tests for Phase 3:** (~50 lines)
- Test CLI streaming callback setup
- Test response box borders with streaming
- Test fallback when streaming disabled

---

### Phase 4: API Server real streaming

**File: gateway/platforms/api_server.py**

Replace the pseudo-streaming `_write_sse_chat_completion()` with real
token-by-token SSE when the agent supports it.

#### 4a. Wire streaming callback for stream=true requests (~20 lines)

```python
if stream:
    _stream_q = queue.Queue()
    
    def _api_stream_callback(delta):
        _stream_q.put(delta)  # None = done
    
    # Pass callback to _run_agent
    result, usage = await self._run_agent(
        ..., stream_callback=_api_stream_callback,
    )
```

#### 4b. Real SSE writer (~40 lines)

```python
async def _write_real_sse(self, request, completion_id, model, stream_q):
    response = web.StreamResponse(
        headers={"Content-Type": "text/event-stream", "Cache-Control": "no-cache"},
    )
    await response.prepare(request)
    
    # Role chunk
    await response.write(...)
    
    # Stream content chunks as they arrive
    while True:
        try:
            delta = await asyncio.get_event_loop().run_in_executor(
                None, lambda: stream_q.get(timeout=0.1)
            )
        except queue.Empty:
            continue
        
        if delta is None:  # End of stream
            break
        
        chunk = {"id": completion_id, "object": "chat.completion.chunk", ...
                 "choices": [{"delta": {"content": delta}, ...}]}
        await response.write(f"data: {json.dumps(chunk)}\n\n".encode())
    
    # Finish + [DONE]
    await response.write(...)
    await response.write(b"data: [DONE]\n\n")
    return response
```

**Challenge: concurrent execution**

The agent runs in a thread executor. SSE writing happens in the async event
loop. The queue bridges them. But `_run_agent()` currently awaits the full
result before returning. For real streaming, we need to start the agent in
the background and stream tokens while it runs:

```python
# Start agent in background
agent_task = asyncio.create_task(self._run_agent_async(...))

# Stream tokens while agent runs
await self._write_real_sse(request, ..., stream_q)

# Agent is done by now (stream_q received None)
result, usage = await agent_task
```

This requires splitting `_run_agent` into an async version that doesn't
block waiting for the result, or running it in a separate task.

**Responses API SSE format:**

For `/v1/responses` with `stream=true`, the SSE events are different:

```
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"Hello"}

event: response.completed  
data: {"type":"response.completed","response":{...}}
```

This needs a separate SSE writer that emits Responses API format events.

**Tests for Phase 4:** (~80 lines)
- Test real SSE streaming with mocked agent
- Test SSE event format (Chat Completions vs Responses)
- Test client disconnect during streaming
- Test fallback to pseudo-streaming when callback not available

---

## Integration Issues & Edge Cases

### 1. Tool calls during streaming

When the model returns tool calls instead of text, no text tokens are emitted.
The stream_callback is simply never called with text. After tools execute, the
next API call may produce the final text response — streaming picks up again.

The stream preview task needs to handle this: if no tokens arrive during a
tool-call round, don't send/edit any message. The tool progress messages
continue working as before.

### 2. Duplicate messages

The biggest risk: the agent sends the final response normally (via the
existing send path) AND the stream preview already showed it. The user
sees the response twice.

Prevention: when streaming is active and tokens were delivered, the final
response send must be suppressed. The `result["_streamed_msg_id"]` marker
tells the base adapter to skip its normal send.

### 3. Response post-processing

The final response may differ from the accumulated streamed tokens:
- Think block stripping (`<think>...</think>` removed)
- Trailing whitespace cleanup
- Tool result media tag appending

The stream preview shows raw tokens. The final edit should use the
post-processed version. This means the final edit (removing the cursor)
should use the post-processed `final_response`, not just the accumulated
stream text.

### 4. Context compression during streaming

If the agent triggers context compression mid-conversation, the streaming
tokens from BEFORE compression are from a different context than those
after. This isn't a problem in practice — compression happens between
API calls, not during streaming.

### 5. Interrupt during streaming

User sends a new message while streaming → interrupt. The stream is killed
(HTTP connection closed), accumulated tokens are shown as-is (no cursor),
and the interrupt message is processed normally. This is already handled by
`_interruptible_api_call` closing the client.

### 6. Multi-model / fallback

If the primary model fails and the agent falls back to a different model,
streaming state resets. The fallback call may or may not support streaming.
The graceful fallback in `_run_streaming_chat_completion` handles this.

### 7. Rate limiting on edits

Telegram: ~20 edits/minute (~1 every 3 seconds to be safe)
Discord: 5 edits per 5 seconds per message
Slack: ~50 API calls/minute

The 1.5s edit interval is conservative enough for all platforms. If we get
429 rate limit errors on edits, just skip that edit cycle and try next time.

---

## Files Changed Summary

| File | Phase | Changes |
|------|-------|---------|
| `run_agent.py` | 1 | +stream_callback param, +_run_streaming_chat_completion(), modify _run_codex_stream(), modify _interruptible_api_call() |
| `gateway/run.py` | 2 | +streaming config reader, +queue/callback setup, +stream_preview task, +skip-final-send logic |
| `gateway/platforms/base.py` | 2 | +check for _streamed_msg_id in response handler |
| `cli.py` | 3 | +streaming setup, +token display, +response box integration |
| `gateway/platforms/api_server.py` | 4 | +real SSE writer, +streaming callback wiring |
| `hermes_cli/config.py` | 1 | +streaming config defaults |
| `cli-config.yaml.example` | 1 | +streaming section |
| `tests/test_streaming.py` | 1-4 | NEW — ~380 lines of tests |

**Total new code**: ~500 lines across all phases
**Total test code**: ~380 lines

---

## Rollout Plan

1. **Phase 1** (core): Merge to main. Streaming disabled by default.
   Zero impact on existing behavior. Can be tested with env var.

2. **Phase 2** (gateway): Merge to main. Test on Telegram manually.
   Enable per-platform: `streaming.telegram: true` in config.

3. **Phase 3** (CLI): Merge to main. Test in terminal.
   Enable: `streaming.cli: true` or `streaming.enabled: true`.

4. **Phase 4** (API server): Merge to main. Test with Open WebUI.
   Auto-enabled when client sends `stream: true`.

Each phase is independently mergeable and testable. Streaming stays
off by default throughout. Once all phases are stable, consider
changing the default to enabled.

---

## Config Reference (final state)

```yaml
# config.yaml
streaming:
  enabled: false          # Master switch (default: off)
  cli: true               # Per-platform override
  telegram: true
  discord: true
  slack: true
  api_server: true        # API server always streams when client requests it
  edit_interval: 1.5      # Seconds between message edits (default: 1.5)
  min_tokens: 20          # Tokens before first display (default: 20)
```

```bash
# Environment variable override
HERMES_STREAMING_ENABLED=true
```


================================================
FILE: AGENTS.md
================================================
# Hermes Agent - Development Guide

Instructions for AI coding assistants and developers working on the hermes-agent codebase.

## Development Environment

```bash
source venv/bin/activate  # ALWAYS activate before running Python
```

## Project Structure

```
hermes-agent/
├── run_agent.py          # AIAgent class — core conversation loop
├── model_tools.py        # Tool orchestration, _discover_tools(), handle_function_call()
├── toolsets.py           # Toolset definitions, _HERMES_CORE_TOOLS list
├── cli.py                # HermesCLI class — interactive CLI orchestrator
├── hermes_state.py       # SessionDB — SQLite session store (FTS5 search)
├── agent/                # Agent internals
│   ├── prompt_builder.py     # System prompt assembly
│   ├── context_compressor.py # Auto context compression
│   ├── prompt_caching.py     # Anthropic prompt caching
│   ├── auxiliary_client.py   # Auxiliary LLM client (vision, summarization)
│   ├── model_metadata.py     # Model context lengths, token estimation
│   ├── models_dev.py         # models.dev registry integration (provider-aware context)
│   ├── display.py            # KawaiiSpinner, tool preview formatting
│   ├── skill_commands.py     # Skill slash commands (shared CLI/gateway)
│   └── trajectory.py         # Trajectory saving helpers
├── hermes_cli/           # CLI subcommands and setup
│   ├── main.py           # Entry point — all `hermes` subcommands
│   ├── config.py         # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration
│   ├── commands.py       # Slash command definitions + SlashCommandCompleter
│   ├── callbacks.py      # Terminal callbacks (clarify, sudo, approval)
│   ├── setup.py          # Interactive setup wizard
│   ├── skin_engine.py    # Skin/theme engine — CLI visual customization
│   ├── skills_config.py  # `hermes skills` — enable/disable skills per platform
│   ├── tools_config.py   # `hermes tools` — enable/disable tools per platform
│   ├── skills_hub.py     # `/skills` slash command (search, browse, install)
│   ├── models.py         # Model catalog, provider model lists
│   └── auth.py           # Provider credential resolution
├── tools/                # Tool implementations (one file per tool)
│   ├── registry.py       # Central tool registry (schemas, handlers, dispatch)
│   ├── approval.py       # Dangerous command detection
│   ├── terminal_tool.py  # Terminal orchestration
│   ├── process_registry.py # Background process management
│   ├── file_tools.py     # File read/write/search/patch
│   ├── web_tools.py      # Web search/extract (Parallel + Firecrawl)
│   ├── browser_tool.py   # Browserbase browser automation
│   ├── code_execution_tool.py # execute_code sandbox
│   ├── delegate_tool.py  # Subagent delegation
│   ├── mcp_tool.py       # MCP client (~1050 lines)
│   └── environments/     # Terminal backends (local, docker, ssh, modal, daytona, singularity)
├── gateway/              # Messaging platform gateway
│   ├── run.py            # Main loop, slash commands, message dispatch
│   ├── session.py        # SessionStore — conversation persistence
│   └── platforms/        # Adapters: telegram, discord, slack, whatsapp, homeassistant, signal
├── acp_adapter/          # ACP server (VS Code / Zed / JetBrains integration)
├── cron/                 # Scheduler (jobs.py, scheduler.py)
├── environments/         # RL training environments (Atropos)
├── tests/                # Pytest suite (~3000 tests)
└── batch_runner.py       # Parallel batch processing
```

**User config:** `~/.hermes/config.yaml` (settings), `~/.hermes/.env` (API keys)

## File Dependency Chain

```
tools/registry.py  (no deps — imported by all tool files)
       ↑
tools/*.py  (each calls registry.register() at import time)
       ↑
model_tools.py  (imports tools/registry + triggers tool discovery)
       ↑
run_agent.py, cli.py, batch_runner.py, environments/
```

---

## AIAgent Class (run_agent.py)

```python
class AIAgent:
    def __init__(self,
        model: str = "anthropic/claude-opus-4.6",
        max_iterations: int = 90,
        enabled_toolsets: list = None,
        disabled_toolsets: list = None,
        quiet_mode: bool = False,
        save_trajectories: bool = False,
        platform: str = None,           # "cli", "telegram", etc.
        session_id: str = None,
        skip_context_files: bool = False,
        skip_memory: bool = False,
        # ... plus provider, api_mode, callbacks, routing params
    ): ...

    def chat(self, message: str) -> str:
        """Simple interface — returns final response string."""

    def run_conversation(self, user_message: str, system_message: str = None,
                         conversation_history: list = None, task_id: str = None) -> dict:
        """Full interface — returns dict with final_response + messages."""
```

### Agent Loop

The core loop is inside `run_conversation()` — entirely synchronous:

```python
while api_call_count < self.max_iterations and self.iteration_budget.remaining > 0:
    response = client.chat.completions.create(model=model, messages=messages, tools=tool_schemas)
    if response.tool_calls:
        for tool_call in response.tool_calls:
            result = handle_function_call(tool_call.name, tool_call.args, task_id)
            messages.append(tool_result_message(result))
        api_call_count += 1
    else:
        return response.content
```

Messages follow OpenAI format: `{"role": "system/user/assistant/tool", ...}`. Reasoning content is stored in `assistant_msg["reasoning"]`.

---

## CLI Architecture (cli.py)

- **Rich** for banner/panels, **prompt_toolkit** for input with autocomplete
- **KawaiiSpinner** (`agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results
- `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML
- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text
- `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry
- Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching

### Slash Command Registry (`hermes_cli/commands.py`)

All slash commands are defined in a central `COMMAND_REGISTRY` list of `CommandDef` objects. Every downstream consumer derives from this registry automatically:

- **CLI** — `process_command()` resolves aliases via `resolve_command()`, dispatches on canonical name
- **Gateway** — `GATEWAY_KNOWN_COMMANDS` frozenset for hook emission, `resolve_command()` for dispatch
- **Gateway help** — `gateway_help_lines()` generates `/help` output
- **Telegram** — `telegram_bot_commands()` generates the BotCommand menu
- **Slack** — `slack_subcommand_map()` generates `/hermes` subcommand routing
- **Autocomplete** — `COMMANDS` flat dict feeds `SlashCommandCompleter`
- **CLI help** — `COMMANDS_BY_CATEGORY` dict feeds `show_help()`

### Adding a Slash Command

1. Add a `CommandDef` entry to `COMMAND_REGISTRY` in `hermes_cli/commands.py`:
```python
CommandDef("mycommand", "Description of what it does", "Session",
           aliases=("mc",), args_hint="[arg]"),
```
2. Add handler in `HermesCLI.process_command()` in `cli.py`:
```python
elif canonical == "mycommand":
    self._handle_mycommand(cmd_original)
```
3. If the command is available in the gateway, add a handler in `gateway/run.py`:
```python
if canonical == "mycommand":
    return await self._handle_mycommand(event)
```
4. For persistent settings, use `save_config_value()` in `cli.py`

**CommandDef fields:**
- `name` — canonical name without slash (e.g. `"background"`)
- `description` — human-readable description
- `category` — one of `"Session"`, `"Configuration"`, `"Tools & Skills"`, `"Info"`, `"Exit"`
- `aliases` — tuple of alternative names (e.g. `("bg",)`)
- `args_hint` — argument placeholder shown in help (e.g. `"<prompt>"`, `"[name]"`)
- `cli_only` — only available in the interactive CLI
- `gateway_only` — only available in messaging platforms

**Adding an alias** requires only adding it to the `aliases` tuple on the existing `CommandDef`. No other file changes needed — dispatch, help text, Telegram menu, Slack mapping, and autocomplete all update automatically.

---

## Adding New Tools

Requires changes in **3 files**:

**1. Create `tools/your_tool.py`:**
```python
import json, os
from tools.registry import registry

def check_requirements() -> bool:
    return bool(os.getenv("EXAMPLE_API_KEY"))

def example_tool(param: str, task_id: str = None) -> str:
    return json.dumps({"success": True, "data": "..."})

registry.register(
    name="example_tool",
    toolset="example",
    schema={"name": "example_tool", "description": "...", "parameters": {...}},
    handler=lambda args, **kw: example_tool(param=args.get("param", ""), task_id=kw.get("task_id")),
    check_fn=check_requirements,
    requires_env=["EXAMPLE_API_KEY"],
)
```

**2. Add import** in `model_tools.py` `_discover_tools()` list.

**3. Add to `toolsets.py`** — either `_HERMES_CORE_TOOLS` (all platforms) or a new toolset.

The registry handles schema collection, dispatch, availability checking, and error wrapping. All handlers MUST return a JSON string.

**Agent-level tools** (todo, memory): intercepted by `run_agent.py` before `handle_function_call()`. See `todo_tool.py` for the pattern.

---

## Adding Configuration

### config.yaml options:
1. Add to `DEFAULT_CONFIG` in `hermes_cli/config.py`
2. Bump `_config_version` (currently 5) to trigger migration for existing users

### .env variables:
1. Add to `OPTIONAL_ENV_VARS` in `hermes_cli/config.py` with metadata:
```python
"NEW_API_KEY": {
    "description": "What it's for",
    "prompt": "Display name",
    "url": "https://...",
    "password": True,
    "category": "tool",  # provider, tool, messaging, setting
},
```

### Config loaders (two separate systems):

| Loader | Used by | Location |
|--------|---------|----------|
| `load_cli_config()` | CLI mode | `cli.py` |
| `load_config()` | `hermes tools`, `hermes setup` | `hermes_cli/config.py` |
| Direct YAML load | Gateway | `gateway/run.py` |

---

## Skin/Theme System

The skin engine (`hermes_cli/skin_engine.py`) provides data-driven CLI visual customization. Skins are **pure data** — no code changes needed to add a new skin.

### Architecture

```
hermes_cli/skin_engine.py    # SkinConfig dataclass, built-in skins, YAML loader
~/.hermes/skins/*.yaml       # User-installed custom skins (drop-in)
```

- `init_skin_from_config()` — called at CLI startup, reads `display.skin` from config
- `get_active_skin()` — returns cached `SkinConfig` for the current skin
- `set_active_skin(name)` — switches skin at runtime (used by `/skin` command)
- `load_skin(name)` — loads from user skins first, then built-ins, then falls back to default
- Missing skin values inherit from the `default` skin automatically

### What skins customize

| Element | Skin Key | Used By |
|---------|----------|---------|
| Banner panel border | `colors.banner_border` | `banner.py` |
| Banner panel title | `colors.banner_title` | `banner.py` |
| Banner section headers | `colors.banner_accent` | `banner.py` |
| Banner dim text | `colors.banner_dim` | `banner.py` |
| Banner body text | `colors.banner_text` | `banner.py` |
| Response box border | `colors.response_border` | `cli.py` |
| Spinner faces (waiting) | `spinner.waiting_faces` | `display.py` |
| Spinner faces (thinking) | `spinner.thinking_faces` | `display.py` |
| Spinner verbs | `spinner.thinking_verbs` | `display.py` |
| Spinner wings (optional) | `spinner.wings` | `display.py` |
| Tool output prefix | `tool_prefix` | `display.py` |
| Per-tool emojis | `tool_emojis` | `display.py` → `get_tool_emoji()` |
| Agent name | `branding.agent_name` | `banner.py`, `cli.py` |
| Welcome message | `branding.welcome` | `cli.py` |
| Response box label | `branding.response_label` | `cli.py` |
| Prompt symbol | `branding.prompt_symbol` | `cli.py` |

### Built-in skins

- `default` — Classic Hermes gold/kawaii (the current look)
- `ares` — Crimson/bronze war-god theme with custom spinner wings
- `mono` — Clean grayscale monochrome
- `slate` — Cool blue developer-focused theme

### Adding a built-in skin

Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`:

```python
"mytheme": {
    "name": "mytheme",
    "description": "Short description",
    "colors": { ... },
    "spinner": { ... },
    "branding": { ... },
    "tool_prefix": "┊",
},
```

### User skins (YAML)

Users create `~/.hermes/skins/<name>.yaml`:

```yaml
name: cyberpunk
description: Neon-soaked terminal theme

colors:
  banner_border: "#FF00FF"
  banner_title: "#00FFFF"
  banner_accent: "#FF1493"

spinner:
  thinking_verbs: ["jacking in", "decrypting", "uploading"]
  wings:
    - ["⟨⚡", "⚡⟩"]

branding:
  agent_name: "Cyber Agent"
  response_label: " ⚡ Cyber "

tool_prefix: "▏"
```

Activate with `/skin cyberpunk` or `display.skin: cyberpunk` in config.yaml.

---

## Important Policies
### Prompt Caching Must Not Break

Hermes-Agent ensures caching remains valid throughout a conversation. **Do NOT implement changes that would:**
- Alter past context mid-conversation
- Change toolsets mid-conversation
- Reload memories or rebuild system prompts mid-conversation

Cache-breaking forces dramatically higher costs. The ONLY time we alter context is during context compression.

### Working Directory Behavior
- **CLI**: Uses current directory (`.` → `os.getcwd()`)
- **Messaging**: Uses `MESSAGING_CWD` env var (default: home directory)

### Background Process Notifications (Gateway)

When `terminal(background=true, check_interval=...)` is used, the gateway runs a watcher that
pushes status updates to the user's chat. Control verbosity with `display.background_process_notifications`
in config.yaml (or `HERMES_BACKGROUND_NOTIFICATIONS` env var):

- `all` — running-output updates + final message (default)
- `result` — only the final completion message
- `error` — only the final message when exit code != 0
- `off` — no watcher messages at all

---

## Known Pitfalls

### DO NOT use `simple_term_menu` for interactive menus
Rendering bugs in tmux/iTerm2 — ghosting on scroll. Use `curses` (stdlib) instead. See `hermes_cli/tools_config.py` for the pattern.

### DO NOT use `\033[K` (ANSI erase-to-EOL) in spinner/display code
Leaks as literal `?[K` text under `prompt_toolkit`'s `patch_stdout`. Use space-padding: `f"\r{line}{' ' * pad}"`.

### `_last_resolved_tool_names` is a process-global in `model_tools.py`
`_run_single_child()` in `delegate_tool.py` saves and restores this global around subagent execution. If you add new code that reads this global, be aware it may be temporarily stale during child agent runs.

### DO NOT hardcode cross-tool references in schema descriptions
Tool schema descriptions must not mention tools from other toolsets by name (e.g., `browser_navigate` saying "prefer web_search"). Those tools may be unavailable (missing API keys, disabled toolset), causing the model to hallucinate calls to non-existent tools. If a cross-reference is needed, add it dynamically in `get_tool_definitions()` in `model_tools.py` — see the `browser_navigate` / `execute_code` post-processing blocks for the pattern.

### Tests must not write to `~/.hermes/`
The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.hermes/` paths in tests.

---

## Testing

```bash
source venv/bin/activate
python -m pytest tests/ -q          # Full suite (~3000 tests, ~3 min)
python -m pytest tests/test_model_tools.py -q   # Toolset resolution
python -m pytest tests/test_cli_init.py -q       # CLI config loading
python -m pytest tests/gateway/ -q               # Gateway tests
python -m pytest tests/tools/ -q                 # Tool-level tests
```

Always run the full suite before pushing changes.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Hermes Agent

Thank you for contributing to Hermes Agent! This guide covers everything you need: setting up your dev environment, understanding the architecture, deciding what to build, and getting your PR merged.

---

## Contribution Priorities

We value contributions in this order:

1. **Bug fixes** — crashes, incorrect behavior, data loss. Always top priority.
2. **Cross-platform compatibility** — Windows, macOS, different Linux distros, different terminal emulators. We want Hermes to work everywhere.
3. **Security hardening** — shell injection, prompt injection, path traversal, privilege escalation. See [Security](#security-considerations).
4. **Performance and robustness** — retry logic, error handling, graceful degradation.
5. **New skills** — but only broadly useful ones. See [Should it be a Skill or a Tool?](#should-it-be-a-skill-or-a-tool)
6. **New tools** — rarely needed. Most capabilities should be skills. See below.
7. **Documentation** — fixes, clarifications, new examples.

---

## Should it be a Skill or a Tool?

This is the most common question for new contributors. The answer is almost always **skill**.

### Make it a Skill when:

- The capability can be expressed as instructions + shell commands + existing tools
- It wraps an external CLI or API that the agent can call via `terminal` or `web_extract`
- It doesn't need custom Python integration or API key management baked into the agent
- Examples: arXiv search, git workflows, Docker management, PDF processing, email via CLI tools

### Make it a Tool when:

- It requires end-to-end integration with API keys, auth flows, or multi-component configuration managed by the agent harness
- It needs custom processing logic that must execute precisely every time (not "best effort" from LLM interpretation)
- It handles binary data, streaming, or real-time events that can't go through the terminal
- Examples: browser automation (Browserbase session management), TTS (audio encoding + platform delivery), vision analysis (base64 image handling)

### Should the Skill be bundled?

Bundled skills (in `skills/`) ship with every Hermes install. They should be **broadly useful to most users**:

- Document handling, web research, common dev workflows, system administration
- Used regularly by a wide range of people

If your skill is official and useful but not universally needed (e.g., a paid service integration, a heavyweight dependency), put it in **`optional-skills/`** — it ships with the repo but isn't activated by default. Users can discover it via `hermes skills browse` (labeled "official") and install it with `hermes skills install` (no third-party warning, builtin trust).

If your skill is specialized, community-contributed, or niche, it's better suited for a **Skills Hub** — upload it to a skills registry and share it in the [Nous Research Discord](https://discord.gg/NousResearch). Users can install it with `hermes skills install`.

---

## Development Setup

### Prerequisites

| Requirement | Notes |
|-------------|-------|
| **Git** | With `--recurse-submodules` support |
| **Python 3.11+** | uv will install it if missing |
| **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) |
| **Node.js 18+** | Optional — needed for browser tools and WhatsApp bridge |

### Clone and install

```bash
git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git
cd hermes-agent

# Create venv with Python 3.11
uv venv venv --python 3.11
export VIRTUAL_ENV="$(pwd)/venv"

# Install with all extras (messaging, cron, CLI menus, dev tools)
uv pip install -e ".[all,dev]"
uv pip install -e "./mini-swe-agent"
uv pip install -e "./tinker-atropos"

# Optional: browser tools
npm install
```

### Configure for development

```bash
mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
cp cli-config.yaml.example ~/.hermes/config.yaml
touch ~/.hermes/.env

# Add at minimum an LLM provider key:
echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.hermes/.env
```

### Run

```bash
# Symlink for global access
mkdir -p ~/.local/bin
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes

# Verify
hermes doctor
hermes chat -q "Hello"
```

### Run tests

```bash
pytest tests/ -v
```

---

## Project Structure

```
hermes-agent/
├── run_agent.py              # AIAgent class — core conversation loop, tool dispatch, session persistence
├── cli.py                    # HermesCLI class — interactive TUI, prompt_toolkit integration
├── model_tools.py            # Tool orchestration (thin layer over tools/registry.py)
├── toolsets.py               # Tool groupings and presets (hermes-cli, hermes-telegram, etc.)
├── hermes_state.py           # SQLite session database with FTS5 full-text search, session titles
├── batch_runner.py           # Parallel batch processing for trajectory generation
│
├── agent/                    # Agent internals (extracted modules)
│   ├── prompt_builder.py         # System prompt assembly (identity, skills, context files, memory)
│   ├── context_compressor.py     # Auto-summarization when approaching context limits
│   ├── auxiliary_client.py       # Resolves auxiliary OpenAI clients (summarization, vision)
│   ├── display.py                # KawaiiSpinner, tool progress formatting
│   ├── model_metadata.py         # Model context lengths, token estimation
│   └── trajectory.py             # Trajectory saving helpers
│
├── hermes_cli/               # CLI command implementations
│   ├── main.py                   # Entry point, argument parsing, command dispatch
│   ├── config.py                 # Config management, migration, env var definitions
│   ├── setup.py                  # Interactive setup wizard
│   ├── auth.py                   # Provider resolution, OAuth, Nous Portal
│   ├── models.py                 # OpenRouter model selection lists
│   ├── banner.py                 # Welcome banner, ASCII art
│   ├── commands.py               # Central slash command registry (CommandDef), autocomplete, gateway helpers
│   ├── callbacks.py              # Interactive callbacks (clarify, sudo, approval)
│   ├── doctor.py                 # Diagnostics
│   ├── skills_hub.py             # Skills Hub CLI + /skills slash command
│   └── skin_engine.py            # Skin/theme engine — data-driven CLI visual customization
│
├── tools/                    # Tool implementations (self-registering)
│   ├── registry.py               # Central tool registry (schemas, handlers, dispatch)
│   ├── approval.py               # Dangerous command detection + per-session approval
│   ├── terminal_tool.py          # Terminal orchestration (sudo, env lifecycle, backends)
│   ├── file_operations.py        # read_file, write_file, search, patch, etc.
│   ├── web_tools.py              # web_search, web_extract (Parallel/Firecrawl + Gemini summarization)
│   ├── vision_tools.py           # Image analysis via multimodal models
│   ├── delegate_tool.py          # Subagent spawning and parallel task execution
│   ├── code_execution_tool.py    # Sandboxed Python with RPC tool access
│   ├── session_search_tool.py    # Search past conversations with FTS5 + summarization
│   ├── cronjob_tools.py          # Scheduled task management
│   ├── skill_tools.py            # Skill search, load, manage
│   └── environments/             # Terminal execution backends
│       ├── base.py                   # BaseEnvironment ABC
│       ├── local.py, docker.py, ssh.py, singularity.py, modal.py, daytona.py
│
├── gateway/                  # Messaging gateway
│   ├── run.py                    # GatewayRunner — platform lifecycle, message routing, cron
│   ├── config.py                 # Platform configuration resolution
│   ├── session.py                # Session store, context prompts, reset policies
│   └── platforms/                # Platform adapters
│       ├── telegram.py, discord_adapter.py, slack.py, whatsapp.py
│
├── scripts/                  # Installer and bridge scripts
│   ├── install.sh                # Linux/macOS installer
│   ├── install.ps1               # Windows PowerShell installer
│   └── whatsapp-bridge/          # Node.js WhatsApp bridge (Baileys)
│
├── skills/                   # Bundled skills (copied to ~/.hermes/skills/ on install)
├── optional-skills/          # Official optional skills (discoverable via hub, not activated by default)
├── environments/             # RL training environments (Atropos integration)
├── tests/                    # Test suite
├── website/                  # Documentation site (hermes-agent.nousresearch.com)
│
├── cli-config.yaml.example   # Example configuration (copied to ~/.hermes/config.yaml)
└── AGENTS.md                 # Development guide for AI coding assistants
```

### User configuration (stored in `~/.hermes/`)

| Path | Purpose |
|------|---------|
| `~/.hermes/config.yaml` | Settings (model, terminal, toolsets, compression, etc.) |
| `~/.hermes/.env` | API keys and secrets |
| `~/.hermes/auth.json` | OAuth credentials (Nous Portal) |
| `~/.hermes/skills/` | All active skills (bundled + hub-installed + agent-created) |
| `~/.hermes/memories/` | Persistent memory (MEMORY.md, USER.md) |
| `~/.hermes/state.db` | SQLite session database |
| `~/.hermes/sessions/` | JSON session logs |
| `~/.hermes/cron/` | Scheduled job data |
| `~/.hermes/whatsapp/session/` | WhatsApp bridge credentials |

---

## Architecture Overview

### Core Loop

```
User message → AIAgent._run_agent_loop()
  ├── Build system prompt (prompt_builder.py)
  ├── Build API kwargs (model, messages, tools, reasoning config)
  ├── Call LLM (OpenAI-compatible API)
  ├── If tool_calls in response:
  │     ├── Execute each tool via registry dispatch
  │     ├── Add tool results to conversation
  │     └── Loop back to LLM call
  ├── If text response:
  │     ├── Persist session to DB
  │     └── Return final_response
  └── Context compression if approaching token limit
```

### Key Design Patterns

- **Self-registering tools**: Each tool file calls `registry.register()` at import time. `model_tools.py` triggers discovery by importing all tool modules.
- **Toolset grouping**: Tools are grouped into toolsets (`web`, `terminal`, `file`, `browser`, etc.) that can be enabled/disabled per platform.
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. JSON logs go to `~/.hermes/sessions/`.
- **Ephemeral injection**: System prompts and prefill messages are injected at API call time, never persisted to the database or logs.
- **Provider abstraction**: The agent works with any OpenAI-compatible API. Provider resolution happens at init time (Nous Portal OAuth, OpenRouter API key, or custom endpoint).
- **Provider routing**: When using OpenRouter, `provider_routing` in config.yaml controls provider selection (sort by throughput/latency/price, allow/ignore specific providers, data retention policies). These are injected as `extra_body.provider` in API requests.

---

## Code Style

- **PEP 8** with practical exceptions (we don't enforce strict line length)
- **Comments**: Only when explaining non-obvious intent, trade-offs, or API quirks. Don't narrate what the code does — `# increment counter` adds nothing
- **Error handling**: Catch specific exceptions. Log with `logger.warning()`/`logger.error()` — use `exc_info=True` for unexpected errors so stack traces appear in logs
- **Cross-platform**: Never assume Unix. See [Cross-Platform Compatibility](#cross-platform-compatibility)

---

## Adding a New Tool

Before writing a tool, ask: [should this be a skill instead?](#should-it-be-a-skill-or-a-tool)

Tools self-register with the central registry. Each tool file co-locates its schema, handler, and registration:

```python
"""my_tool — Brief description of what this tool does."""

import json
from tools.registry import registry


def my_tool(param1: str, param2: int = 10, **kwargs) -> str:
    """Handler. Returns a string result (often JSON)."""
    result = do_work(param1, param2)
    return json.dumps(result)


MY_TOOL_SCHEMA = {
    "type": "function",
    "function": {
        "name": "my_tool",
        "description": "What this tool does and when the agent should use it.",
        "parameters": {
            "type": "object",
            "properties": {
                "param1": {"type": "string", "description": "What param1 is"},
                "param2": {"type": "integer", "description": "What param2 is", "default": 10},
            },
            "required": ["param1"],
        },
    },
}


def _check_requirements() -> bool:
    """Return True if this tool's dependencies are available."""
    return True


registry.register(
    name="my_tool",
    toolset="my_toolset",
    schema=MY_TOOL_SCHEMA,
    handler=lambda args, **kw: my_tool(**args, **kw),
    check_fn=_check_requirements,
)
```

Then add the import to `model_tools.py` in the `_modules` list:

```python
_modules = [
    # ... existing modules ...
    "tools.my_tool",
]
```

If it's a new toolset, add it to `toolsets.py` and to the relevant platform presets.

---

## Adding a Skill

Bundled skills live in `skills/` organized by category. Official optional skills use the same structure in `optional-skills/`:

```
skills/
├── research/
│   └── arxiv/
│       ├── SKILL.md              # Required: main instructions
│       └── scripts/              # Optional: helper scripts
│           └── search_arxiv.py
├── productivity/
│   └── ocr-and-documents/
│       ├── SKILL.md
│       ├── scripts/
│       └── references/
└── ...
```

### SKILL.md format

```markdown
---
name: my-skill
description: Brief description (shown in skill search results)
version: 1.0.0
author: Your Name
license: MIT
platforms: [macos, linux]          # Optional — restrict to specific OS platforms
                                   #   Valid: macos, linux, windows
                                   #   Omit to load on all platforms (default)
required_environment_variables:    # Optional — secure setup-on-load metadata
  - name: MY_API_KEY
    prompt: API key
    help: Where to get it
    required_for: full functionality
prerequisites:                     # Optional legacy runtime requirements
  env_vars: [MY_API_KEY]           #   Backward-compatible alias for required env vars
  commands: [curl, jq]             #   Advisory only; does not hide the skill
metadata:
  hermes:
    tags: [Category, Subcategory, Keywords]
    related_skills: [other-skill-name]
    fallback_for_toolsets: [web]       # Optional — show only when toolset is unavailable
    requires_toolsets: [terminal]      # Optional — show only when toolset is available
---

# Skill Title

Brief intro.

## When to Use
Trigger conditions — when should the agent load this skill?

## Quick Reference
Table of common commands or API calls.

## Procedure
Step-by-step instructions the agent follows.

## Pitfalls
Known failure modes and how to handle them.

## Verification
How the agent confirms it worked.
```

### Platform-specific skills

Skills can declare which OS platforms they support via the `platforms` frontmatter field. Skills with this field are automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms.

```yaml
platforms: [macos]            # macOS only (e.g., iMessage, Apple Reminders)
platforms: [macos, linux]     # macOS and Linux
platforms: [windows]          # Windows only
```

If the field is omitted or empty, the skill loads on all platforms (backward compatible). See `skills/apple/` for examples of macOS-only skills.

### Conditional skill activation

Skills can declare conditions that control when they appear in the system prompt, based on which tools and toolsets are available in the current session. This is primarily used for **fallback skills** — alternatives that should only be shown when a primary tool is unavailable.

Four fields are supported under `metadata.hermes`:

```yaml
metadata:
  hermes:
    fallback_for_toolsets: [web]      # Show ONLY when these toolsets are unavailable
    requires_toolsets: [terminal]     # Show ONLY when these toolsets are available
    fallback_for_tools: [web_search]  # Show ONLY when these specific tools are unavailable
    requires_tools: [terminal]        # Show ONLY when these specific tools are available
```

**Semantics:**
- `fallback_for_*`: The skill is a backup. It is **hidden** when the listed tools/toolsets are available, and **shown** when they are unavailable. Use this for free alternatives to premium tools.
- `requires_*`: The skill needs certain tools to function. It is **hidden** when the listed tools/toolsets are unavailable. Use this for skills that depend on specific capabilities (e.g., a skill that only makes sense with terminal access).
- If both are specified, both conditions must be satisfied for the skill to appear.
- If neither is specified, the skill is always shown (backward compatible).

**Examples:**

```yaml
# DuckDuckGo search — shown when Firecrawl (web toolset) is unavailable
metadata:
  hermes:
    fallback_for_toolsets: [web]

# Smart home skill — only useful when terminal is available
metadata:
  hermes:
    requires_toolsets: [terminal]

# Local browser fallback — shown when Browserbase is unavailable
metadata:
  hermes:
    fallback_for_toolsets: [browser]
```

The filtering happens at prompt build time in `agent/prompt_builder.py`. The `build_skills_system_prompt()` function receives the set of available tools and toolsets from the agent and uses `_skill_should_show()` to evaluate each skill's conditions.

### Skill setup metadata

Skills can declare secure setup-on-load metadata via the `required_environment_variables` frontmatter field. Missing values do not hide the skill from discovery; they trigger a CLI-only secure prompt when the skill is actually loaded.

```yaml
required_environment_variables:
  - name: TENOR_API_KEY
    prompt: Tenor API key
    help: Get a key from https://developers.google.com/tenor
    required_for: full functionality
```

The user may skip setup and keep loading the skill. Hermes only exposes metadata (`stored_as`, `skipped`, `validated`) to the model — never the secret value.

Legacy `prerequisites.env_vars` remains supported and is normalized into the new representation.

```yaml
prerequisites:
  env_vars: [TENOR_API_KEY]       # Legacy alias for required_environment_variables
  commands: [curl, jq]            # Advisory CLI checks
```

Gateway and messaging sessions never collect secrets in-band; they instruct the user to run `hermes setup` or update `~/.hermes/.env` locally.

**When to declare required environment variables:**
- The skill uses an API key or token that should be collected securely at load time
- The skill can still be useful if the user skips setup, but may degrade gracefully

**When to declare command prerequisites:**
- The skill relies on a CLI tool that may not be installed (e.g., `himalaya`, `openhue`, `ddgs`)
- Treat command checks as guidance, not discovery-time hiding

See `skills/gifs/gif-search/` and `skills/email/himalaya/` for examples.

### Skill guidelines

- **No external dependencies unless absolutely necessary.** Prefer stdlib Python, curl, and existing Hermes tools (`web_extract`, `terminal`, `read_file`).
- **Progressive disclosure.** Put the most common workflow first. Edge cases and advanced usage go at the bottom.
- **Include helper scripts** for XML/JSON parsing or complex logic — don't expect the LLM to write parsers inline every time.
- **Test it.** Run `hermes --toolsets skills -q "Use the X skill to do Y"` and verify the agent follows the instructions correctly.

---

## Adding a Skin / Theme

Hermes uses a data-driven skin system — no code changes needed to add a new skin.

**Option A: User skin (YAML file)**

Create `~/.hermes/skins/<name>.yaml`:

```yaml
name: mytheme
description: Short description of the theme

colors:
  banner_border: "#HEX"     # Panel border color
  banner_title: "#HEX"      # Panel title color
  banner_accent: "#HEX"     # Section header color
  banner_dim: "#HEX"        # Muted/dim text color
  banner_text: "#HEX"       # Body text color
  response_border: "#HEX"   # Response box border

spinner:
  waiting_faces: ["(⚔)", "(⛨)"]
  thinking_faces: ["(⚔)", "(⌁)"]
  thinking_verbs: ["forging", "plotting"]
  wings:                     # Optional left/right decorations
    - ["⟪⚔", "⚔⟫"]

branding:
  agent_name: "My Agent"
  welcome: "Welcome message"
  response_label: " ⚔ Agent "
  prompt_symbol: "⚔ ❯ "

tool_prefix: "╎"             # Tool output line prefix
```

All fields are optional — missing values inherit from the default skin.

**Option B: Built-in skin**

Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`. Use the same schema as above but as a Python dict. Built-in skins ship with the package and are always available.

**Activating:**
- CLI: `/skin mytheme` or set `display.skin: mytheme` in config.yaml
- Config: `display: { skin: mytheme }`

See `hermes_cli/skin_engine.py` for the full schema and existing skins as examples.

---

## Cross-Platform Compatibility

Hermes runs on Linux, macOS, and Windows. When writing code that touches the OS:

### Critical rules

1. **`termios` and `fcntl` are Unix-only.** Always catch both `ImportError` and `NotImplementedError`:
   ```python
   try:
       from simple_term_menu import TerminalMenu
       menu = TerminalMenu(options)
       idx = menu.show()
   except (ImportError, NotImplementedError):
       # Fallback: numbered menu for Windows
       for i, opt in enumerate(options):
           print(f"  {i+1}. {opt}")
       idx = int(input("Choice: ")) - 1
   ```

2. **File encoding.** Windows may save `.env` files in `cp1252`. Always handle encoding errors:
   ```python
   try:
       load_dotenv(env_path)
   except UnicodeDecodeError:
       load_dotenv(env_path, encoding="latin-1")
   ```

3. **Process management.** `os.setsid()`, `os.killpg()`, and signal handling differ on Windows. Use platform checks:
   ```python
   import platform
   if platform.system() != "Windows":
       kwargs["preexec_fn"] = os.setsid
   ```

4. **Path separators.** Use `pathlib.Path` instead of string concatenation with `/`.

5. **Shell commands in installers.** If you change `scripts/install.sh`, check if the equivalent change is needed in `scripts/install.ps1`.

---

## Security Considerations

Hermes has terminal access. Security matters.

### Existing protections

| Layer | Implementation |
|-------|---------------|
| **Sudo password piping** | Uses `shlex.quote()` to prevent shell injection |
| **Dangerous command detection** | Regex patterns in `tools/approval.py` with user approval flow |
| **Cron prompt injection** | Scanner in `tools/cronjob_tools.py` blocks instruction-override patterns |
| **Write deny list** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`) resolved via `os.path.realpath()` to prevent symlink bypass |
| **Skills guard** | Security scanner for hub-installed skills (`tools/skills_guard.py`) |
| **Code execution sandbox** | `execute_code` child process runs with API keys stripped from environment |
| **Container hardening** | Docker: all capabilities dropped, no privilege escalation, PID limits, size-limited tmpfs |

### When contributing security-sensitive code

- **Always use `shlex.quote()`** when interpolating user input into shell commands
- **Resolve symlinks** with `os.path.realpath()` before path-based access control checks
- **Don't log secrets.** API keys, tokens, and passwords should never appear in log output
- **Catch broad exceptions** around tool execution so a single failure doesn't crash the agent loop
- **Test on all platforms** if your change touches file paths, process management, or shell commands

If your PR affects security, note it explicitly in the description.

---

## Pull Request Process

### Branch naming

```
fix/description        # Bug fixes
feat/description       # New features
docs/description       # Documentation
test/description       # Tests
refactor/description   # Code restructuring
```

### Before submitting

1. **Run tests**: `pytest tests/ -v`
2. **Test manually**: Run `hermes` and exercise the code path you changed
3. **Check cross-platform impact**: If you touch file I/O, process management, or terminal handling, consider Windows and macOS
4. **Keep PRs focused**: One logical change per PR. Don't mix a bug fix with a refactor with a new feature.

### PR description

Include:
- **What** changed and **why**
- **How to test** it (reproduction steps for bugs, usage examples for features)
- **What platforms** you tested on
- Reference any related issues

### Commit messages

We use [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>(<scope>): <description>
```

| Type | Use for |
|------|---------|
| `fix` | Bug fixes |
| `feat` | New features |
| `docs` | Documentation |
| `test` | Tests |
| `refactor` | Code restructuring (no behavior change) |
| `chore` | Build, CI, dependency updates |

Scopes: `cli`, `gateway`, `tools`, `skills`, `agent`, `install`, `whatsapp`, `security`, etc.

Examples:
```
fix(cli): prevent crash in save_config_value when model is a string
feat(gateway): add WhatsApp multi-user session isolation
fix(security): prevent shell injection in sudo password piping
test(tools): add unit tests for file_operations
```

---

## Reporting Issues

- Use [GitHub Issues](https://github.com/NousResearch/hermes-agent/issues)
- Include: OS, Python version, Hermes version (`hermes version`), full error traceback
- Include steps to reproduce
- Check existing issues before creating duplicates
- For security vulnerabilities, please report privately

---

## Community

- **Discord**: [discord.gg/NousResearch](https://discord.gg/NousResearch) — for questions, showcasing projects, and sharing skills
- **GitHub Discussions**: For design proposals and architecture discussions
- **Skills Hub**: Upload specialized skills to a registry and share them with the community

---

## License

By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Nous Research

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
  <img src="assets/banner.png" alt="Hermes Agent" width="100%">
</p>

# Hermes Agent ☤

<p align="center">
  <a href="https://hermes-agent.nousresearch.com/docs/"><img src="https://img.shields.io/badge/Docs-hermes--agent.nousresearch.com-FFD700?style=for-the-badge" alt="Documentation"></a>
  <a href="https://discord.gg/NousResearch"><img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"></a>
  <a href="https://github.com/NousResearch/hermes-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License: MIT"></a>
  <a href="https://nousresearch.com"><img src="https://img.shields.io/badge/Built%20by-Nous%20Research-blueviolet?style=for-the-badge" alt="Built by Nous Research"></a>
</p>

**The self-improving AI agent built by [Nous Research](https://nousresearch.com).** It's the only agent with a built-in learning loop — it creates skills from experience, improves them during use, nudges itself to persist knowledge, searches its own past conversations, and builds a deepening model of who you are across sessions. Run it on a $5 VPS, a GPU cluster, or serverless infrastructure that costs nearly nothing when idle. It's not tied to your laptop — talk to it from Telegram while it works on a cloud VM.

Use any model you want — [Nous Portal](https://portal.nousresearch.com), [OpenRouter](https://openrouter.ai) (200+ models), [z.ai/GLM](https://z.ai), [Kimi/Moonshot](https://platform.moonshot.ai), [MiniMax](https://www.minimax.io), OpenAI, or your own endpoint. Switch with `hermes model` — no code changes, no lock-in.

<table>
<tr><td><b>A real terminal interface</b></td><td>Full TUI with multiline editing, slash-command autocomplete, conversation history, interrupt-and-redirect, and streaming tool output.</td></tr>
<tr><td><b>Lives where you do</b></td><td>Telegram, Discord, Slack, WhatsApp, Signal, and CLI — all from a single gateway process. Voice memo transcription, cross-platform conversation continuity.</td></tr>
<tr><td><b>A closed learning loop</b></td><td>Agent-curated memory with periodic nudges. Autonomous skill creation after complex tasks. Skills self-improve during use. FTS5 session search with LLM summarization for cross-session recall. <a href="https://github.com/plastic-labs/honcho">Honcho</a> dialectic user modeling. Compatible with the <a href="https://agentskills.io">agentskills.io</a> open standard.</td></tr>
<tr><td><b>Scheduled automations</b></td><td>Built-in cron scheduler with delivery to any platform. Daily reports, nightly backups, weekly audits — all in natural language, running unattended.</td></tr>
<tr><td><b>Delegates and parallelizes</b></td><td>Spawn isolated subagents for parallel workstreams. Write Python scripts that call tools via RPC, collapsing multi-step pipelines into zero-context-cost turns.</td></tr>
<tr><td><b>Runs anywhere, not just your laptop</b></td><td>Six terminal backends — local, Docker, SSH, Daytona, Singularity, and Modal. Daytona and Modal offer serverless persistence — your agent's environment hibernates when idle and wakes on demand, costing nearly nothing between sessions. Run it on a $5 VPS or a GPU cluster.</td></tr>
<tr><td><b>Research-ready</b></td><td>Batch trajectory generation, Atropos RL environments, trajectory compression for training the next generation of tool-calling models.</td></tr>
</table>

---

## Quick Install

```bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
```

Works on Linux, macOS, and WSL2. The installer handles everything — Python, Node.js, dependencies, and the `hermes` command. No prerequisites except git.

> **Windows:** Native Windows is not supported. Please install [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) and run the command above.

After installation:

```bash
source ~/.bashrc    # reload shell (or: source ~/.zshrc)
hermes              # start chatting!
```

---

## Getting Started

```bash
hermes              # Interactive CLI — start a conversation
hermes model        # Choose your LLM provider and model
hermes tools        # Configure which tools are enabled
hermes config set   # Set individual config values
hermes gateway      # Start the messaging gateway (Telegram, Discord, etc.)
hermes setup        # Run the full setup wizard (configures everything at once)
hermes claw migrate # Migrate from OpenClaw (if coming from OpenClaw)
hermes update       # Update to the latest version
hermes doctor       # Diagnose any issues
```

📖 **[Full documentation →](https://hermes-agent.nousresearch.com/docs/)**

## CLI vs Messaging Quick Reference

Hermes has two entry points: start the terminal UI with `hermes`, or run the gateway and talk to it from Telegram, Discord, Slack, WhatsApp, Signal, or Email. Once you're in a conversation, many slash commands are shared across both interfaces.

| Action | CLI | Messaging platforms |
|---------|-----|---------------------|
| Start chatting | `hermes` | Run `hermes gateway setup` + `hermes gateway start`, then send the bot a message |
| Start fresh conversation | `/new` or `/reset` | `/new` or `/reset` |
| Change model | `/model [provider:model]` | `/model [provider:model]` |
| Set a personality | `/personality [name]` | `/personality [name]` |
| Retry or undo the last turn | `/retry`, `/undo` | `/retry`, `/undo` |
| Compress context / check usage | `/compress`, `/usage`, `/insights [--days N]` | `/compress`, `/usage`, `/insights [days]` |
| Browse skills | `/skills` or `/<skill-name>` | `/skills` or `/<skill-name>` |
| Interrupt current work | `Ctrl+C` or send a new message | `/stop` or send a new message |
| Platform-specific status | `/platforms` | `/status`, `/sethome` |

For the full command lists, see the [CLI guide](https://hermes-agent.nousresearch.com/docs/user-guide/cli) and the [Messaging Gateway guide](https://hermes-agent.nousresearch.com/docs/user-guide/messaging).

---

## Documentation

All documentation lives at **[hermes-agent.nousresearch.com/docs](https://hermes-agent.nousresearch.com/docs/)**:

| Section | What's Covered |
|---------|---------------|
| [Quickstart](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart) | Install → setup → first conversation in 2 minutes |
| [CLI Usage](https://hermes-agent.nousresearch.com/docs/user-guide/cli) | Commands, keybindings, personalities, sessions |
| [Configuration](https://hermes-agent.nousresearch.com/docs/user-guide/configuration) | Config file, providers, models, all options |
| [Messaging Gateway](https://hermes-agent.nousresearch.com/docs/user-guide/messaging) | Telegram, Discord, Slack, WhatsApp, Signal, Home Assistant |
| [Security](https://hermes-agent.nousresearch.com/docs/user-guide/security) | Command approval, DM pairing, container isolation |
| [Tools & Toolsets](https://hermes-agent.nousresearch.com/docs/user-guide/features/tools) | 40+ tools, toolset system, terminal backends |
| [Skills System](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills) | Procedural memory, Skills Hub, creating skills |
| [Memory](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory) | Persistent memory, user profiles, best practices |
| [MCP Integration](https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp) | Connect any MCP server for extended capabilities |
| [Cron Scheduling](https://hermes-agent.nousresearch.com/docs/user-guide/features/cron) | Scheduled tasks with platform delivery |
| [Context Files](https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files) | Project context that shapes every conversation |
| [Architecture](https://hermes-agent.nousresearch.com/docs/developer-guide/architecture) | Project structure, agent loop, key classes |
| [Contributing](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) | Development setup, PR process, code style |
| [CLI Reference](https://hermes-agent.nousresearch.com/docs/reference/cli-commands) | All commands and flags |
| [Environment Variables](https://hermes-agent.nousresearch.com/docs/reference/environment-variables) | Complete env var reference |

---

## Migrating from OpenClaw

If you're coming from OpenClaw, Hermes can automatically import your settings, memories, skills, and API keys.

**During first-time setup:** The setup wizard (`hermes setup`) automatically detects `~/.openclaw` and offers to migrate before configuration begins.

**Anytime after install:**

```bash
hermes claw migrate              # Interactive migration (full preset)
hermes claw migrate --dry-run    # Preview what would be migrated
hermes claw migrate --preset user-data   # Migrate without secrets
hermes claw migrate --overwrite  # Overwrite existing conflicts
```

What gets imported:
- **SOUL.md** — persona file
- **Memories** — MEMORY.md and USER.md entries
- **Skills** — user-created skills → `~/.hermes/skills/openclaw-imports/`
- **Command allowlist** — approval patterns
- **Messaging settings** — platform configs, allowed users, working directory
- **API keys** — allowlisted secrets (Telegram, OpenRouter, OpenAI, Anthropic, ElevenLabs)
- **TTS assets** — workspace audio files
- **Workspace instructions** — AGENTS.md (with `--workspace-target`)

See `hermes claw migrate --help` for all options, or use the `openclaw-migration` skill for an interactive agent-guided migration with dry-run previews.

---

## Contributing

We welcome contributions! See the [Contributing Guide](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) for development setup, code style, and PR process.

Quick start for contributors:

```bash
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
git submodule update --init mini-swe-agent   # required terminal backend
curl -LsSf https://astral.sh/uv/install.sh | sh
uv venv venv --python 3.11
source venv/bin/activate
uv pip install -e ".[all,dev]"
uv pip install -e "./mini-swe-agent"
python -m pytest tests/ -q
```

> **RL Training (optional):** To work on the RL/Tinker-Atropos integration, also run:
> ```bash
> git submodule update --init tinker-atropos
> uv pip install -e "./tinker-atropos"
> ```

---

## Community

- 💬 [Discord](https://discord.gg/NousResearch)
- 📚 [Skills Hub](https://agentskills.io)
- 🐛 [Issues](https://github.com/NousResearch/hermes-agent/issues)
- 💡 [Discussions](https://github.com/NousResearch/hermes-agent/discussions)

---

## License

MIT — see [LICENSE](LICENSE).

Built by [Nous Research](https://nousresearch.com).


================================================
FILE: RELEASE_v0.2.0.md
================================================
# Hermes Agent v0.2.0 (v2026.3.12)

**Release Date:** March 12, 2026

> First tagged release since v0.1.0 (the initial pre-public foundation). In just over two weeks, Hermes Agent went from a small internal project to a full-featured AI agent platform — thanks to an explosion of community contributions. This release covers **216 merged pull requests** from **63 contributors**, resolving **119 issues**.

---

## ✨ Highlights

- **Multi-Platform Messaging Gateway** — Telegram, Discord, Slack, WhatsApp, Signal, Email (IMAP/SMTP), and Home Assistant platforms with unified session management, media attachments, and per-platform tool configuration.

- **MCP (Model Context Protocol) Client** — Native MCP support with stdio and HTTP transports, reconnection, resource/prompt discovery, and sampling (server-initiated LLM requests). ([#291](https://github.com/NousResearch/hermes-agent/pull/291) — @0xbyt4, [#301](https://github.com/NousResearch/hermes-agent/pull/301), [#753](https://github.com/NousResearch/hermes-agent/pull/753))

- **Skills Ecosystem** — 70+ bundled and optional skills across 15+ categories with a Skills Hub for community discovery, per-platform enable/disable, conditional activation based on tool availability, and prerequisite validation. ([#743](https://github.com/NousResearch/hermes-agent/pull/743) — @teyrebaz33, [#785](https://github.com/NousResearch/hermes-agent/pull/785) — @teyrebaz33)

- **Centralized Provider Router** — Unified `call_llm()`/`async_call_llm()` API replaces scattered provider logic across vision, summarization, compression, and trajectory saving. All auxiliary consumers route through a single code path with automatic credential resolution. ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))

- **ACP Server** — VS Code, Zed, and JetBrains editor integration via the Agent Communication Protocol standard. ([#949](https://github.com/NousResearch/hermes-agent/pull/949))

- **CLI Skin/Theme Engine** — Data-driven visual customization: banners, spinners, colors, branding. 7 built-in skins + custom YAML skins.

- **Git Worktree Isolation** — `hermes -w` launches isolated agent sessions in git worktrees for safe parallel work on the same repo. ([#654](https://github.com/NousResearch/hermes-agent/pull/654))

- **Filesystem Checkpoints & Rollback** — Automatic snapshots before destructive operations with `/rollback` to restore. ([#824](https://github.com/NousResearch/hermes-agent/pull/824))

- **3,289 Tests** — From near-zero test coverage to a comprehensive test suite covering agent, gateway, tools, cron, and CLI.

---

## 🏗️ Core Agent & Architecture

### Provider & Model Support
- Centralized provider router with `resolve_provider_client()` + `call_llm()` API ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
- Nous Portal as first-class provider in setup ([#644](https://github.com/NousResearch/hermes-agent/issues/644))
- OpenAI Codex (Responses API) with ChatGPT subscription support ([#43](https://github.com/NousResearch/hermes-agent/pull/43)) — @grp06
- Codex OAuth vision support + multimodal content adapter
- Validate `/model` against live API instead of hardcoded lists
- Self-hosted Firecrawl support ([#460](https://github.com/NousResearch/hermes-agent/pull/460)) — @caentzminger
- Kimi Code API support ([#635](https://github.com/NousResearch/hermes-agent/pull/635)) — @christomitov
- MiniMax model ID update ([#473](https://github.com/NousResearch/hermes-agent/pull/473)) — @tars90percent
- OpenRouter provider routing configuration (provider_preferences)
- Nous credential refresh on 401 errors ([#571](https://github.com/NousResearch/hermes-agent/pull/571), [#269](https://github.com/NousResearch/hermes-agent/pull/269)) — @rewbs
- z.ai/GLM, Kimi/Moonshot, MiniMax, Azure OpenAI as first-class providers
- Unified `/model` and `/provider` into single view

### Agent Loop & Conversation
- Simple fallback model for provider resilience ([#740](https://github.com/NousResearch/hermes-agent/pull/740))
- Shared iteration budget across parent + subagent delegation
- Iteration budget pressure via tool result injection
- Configurable subagent provider/model with full credential resolution
- Handle 413 payload-too-large via compression instead of aborting ([#153](https://github.com/NousResearch/hermes-agent/pull/153)) — @tekelala
- Retry with rebuilt payload after compression ([#616](https://github.com/NousResearch/hermes-agent/pull/616)) — @tripledoublev
- Auto-compress pathologically large gateway sessions ([#628](https://github.com/NousResearch/hermes-agent/issues/628))
- Tool call repair middleware — auto-lowercase and invalid tool handler
- Reasoning effort configuration and `/reasoning` command ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
- Detect and block file re-read/search loops after context compression ([#705](https://github.com/NousResearch/hermes-agent/pull/705)) — @0xbyt4

### Session & Memory
- Session naming with unique titles, auto-lineage, rich listing, and resume by name ([#720](https://github.com/NousResearch/hermes-agent/pull/720))
- Interactive session browser with search filtering ([#733](https://github.com/NousResearch/hermes-agent/pull/733))
- Display previous messages when resuming a session ([#734](https://github.com/NousResearch/hermes-agent/pull/734))
- Honcho AI-native cross-session user modeling ([#38](https://github.com/NousResearch/hermes-agent/pull/38)) — @erosika
- Proactive async memory flush on session expiry
- Smart context length probing with persistent caching + banner display
- `/resume` command for switching to named sessions in gateway
- Session reset policy for messaging platforms

---

## 📱 Messaging Platforms (Gateway)

### Telegram
- Native file attachments: send_document + send_video
- Document file processing for PDF, text, and Office files — @tekelala
- Forum topic session isolation ([#766](https://github.com/NousResearch/hermes-agent/pull/766)) — @spanishflu-est1918
- Browser screenshot sharing via MEDIA: protocol ([#657](https://github.com/NousResearch/hermes-agent/pull/657))
- Location support for find-nearby skill
- TTS voice message accumulation fix ([#176](https://github.com/NousResearch/hermes-agent/pull/176)) — @Bartok9
- Improved error handling and logging ([#763](https://github.com/NousResearch/hermes-agent/pull/763)) — @aydnOktay
- Italic regex newline fix + 43 format tests ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4

### Discord
- Channel topic included in session context ([#248](https://github.com/NousResearch/hermes-agent/pull/248)) — @Bartok9
- DISCORD_ALLOW_BOTS config for bot message filtering ([#758](https://github.com/NousResearch/hermes-agent/pull/758))
- Document and video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
- Improved error handling and logging ([#761](https://github.com/NousResearch/hermes-agent/pull/761)) — @aydnOktay

### Slack
- App_mention 404 fix + document/video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
- Structured logging replacing print statements — @aydnOktay

### WhatsApp
- Native media sending — images, videos, documents ([#292](https://github.com/NousResearch/hermes-agent/pull/292)) — @satelerd
- Multi-user session isolation ([#75](https://github.com/NousResearch/hermes-agent/pull/75)) — @satelerd
- Cross-platform port cleanup replacing Linux-only fuser ([#433](https://github.com/NousResearch/hermes-agent/pull/433)) — @Farukest
- DM interrupt key mismatch fix ([#350](https://github.com/NousResearch/hermes-agent/pull/350)) — @Farukest

### Signal
- Full Signal messenger gateway via signal-cli-rest-api ([#405](https://github.com/NousResearch/hermes-agent/issues/405))
- Media URL support in message events ([#871](https://github.com/NousResearch/hermes-agent/pull/871))

### Email (IMAP/SMTP)
- New email gateway platform — @0xbyt4

### Home Assistant
- REST tools + WebSocket gateway integration ([#184](https://github.com/NousResearch/hermes-agent/pull/184)) — @0xbyt4
- Service discovery and enhanced setup
- Toolset mapping fix ([#538](https://github.com/NousResearch/hermes-agent/pull/538)) — @Himess

### Gateway Core
- Expose subagent tool calls and thinking to users ([#186](https://github.com/NousResearch/hermes-agent/pull/186)) — @cutepawss
- Configurable background process watcher notifications ([#840](https://github.com/NousResearch/hermes-agent/pull/840))
- `edit_message()` for Telegram/Discord/Slack with fallback
- `/compress`, `/usage`, `/update` slash commands
- Eliminated 3x SQLite message duplication in gateway sessions ([#873](https://github.com/NousResearch/hermes-agent/pull/873))
- Stabilize system prompt across gateway turns for cache hits ([#754](https://github.com/NousResearch/hermes-agent/pull/754))
- MCP server shutdown on gateway exit ([#796](https://github.com/NousResearch/hermes-agent/pull/796)) — @0xbyt4
- Pass session_db to AIAgent, fixing session_search error ([#108](https://github.com/NousResearch/hermes-agent/pull/108)) — @Bartok9
- Persist transcript changes in /retry, /undo; fix /reset attribute ([#217](https://github.com/NousResearch/hermes-agent/pull/217)) — @Farukest
- UTF-8 encoding fix preventing Windows crashes ([#369](https://github.com/NousResearch/hermes-agent/pull/369)) — @ch3ronsa

---

## 🖥️ CLI & User Experience

### Interactive CLI
- Data-driven skin/theme engine — 7 built-in skins (default, ares, mono, slate, poseidon, sisyphus, charizard) + custom YAML skins
- `/personality` command with custom personality + disable support ([#773](https://github.com/NousResearch/hermes-agent/pull/773)) — @teyrebaz33
- User-defined quick commands that bypass the agent loop ([#746](https://github.com/NousResearch/hermes-agent/pull/746)) — @teyrebaz33
- `/reasoning` command for effort level and display toggle ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
- `/verbose` slash command to toggle debug at runtime ([#94](https://github.com/NousResearch/hermes-agent/pull/94)) — @cesareth
- `/insights` command — usage analytics, cost estimation & activity patterns ([#552](https://github.com/NousResearch/hermes-agent/pull/552))
- `/background` command for managing background processes
- `/help` formatting with command categories
- Bell-on-complete — terminal bell when agent finishes ([#738](https://github.com/NousResearch/hermes-agent/pull/738))
- Up/down arrow history navigation
- Clipboard image paste (Alt+V / Ctrl+V)
- Loading indicators for slow slash commands ([#882](https://github.com/NousResearch/hermes-agent/pull/882))
- Spinner flickering fix under patch_stdout ([#91](https://github.com/NousResearch/hermes-agent/pull/91)) — @0xbyt4
- `--quiet/-Q` flag for programmatic single-query mode
- `--fuck-it-ship-it` flag to bypass all approval prompts ([#724](https://github.com/NousResearch/hermes-agent/pull/724)) — @dmahan93
- Tools summary flag ([#767](https://github.com/NousResearch/hermes-agent/pull/767)) — @luisv-1
- Terminal blinking fix on SSH ([#284](https://github.com/NousResearch/hermes-agent/pull/284)) — @ygd58
- Multi-line paste detection fix ([#84](https://github.com/NousResearch/hermes-agent/pull/84)) — @0xbyt4

### Setup & Configuration
- Modular setup wizard with section subcommands and tool-first UX
- Container resource configuration prompts
- Backend validation for required binaries
- Config migration system (currently v7)
- API keys properly routed to .env instead of config.yaml ([#469](https://github.com/NousResearch/hermes-agent/pull/469)) — @ygd58
- Atomic write for .env to prevent API key loss on crash ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
- `hermes tools` — per-platform tool enable/disable with curses UI
- `hermes doctor` for health checks across all configured providers
- `hermes update` with auto-restart for gateway service
- Show update-available notice in CLI banner
- Multiple named custom providers
- Shell config detection improvement for PATH setup ([#317](https://github.com/NousResearch/hermes-agent/pull/317)) — @mehmetkr-31
- Consistent HERMES_HOME and .env path resolution ([#51](https://github.com/NousResearch/hermes-agent/pull/51), [#48](https://github.com/NousResearch/hermes-agent/pull/48)) — @deankerr
- Docker backend fix on macOS + subagent auth for Nous Portal ([#46](https://github.com/NousResearch/hermes-agent/pull/46)) — @rsavitt

---

## 🔧 Tool System

### MCP (Model Context Protocol)
- Native MCP client with stdio + HTTP transports ([#291](https://github.com/NousResearch/hermes-agent/pull/291) — @0xbyt4, [#301](https://github.com/NousResearch/hermes-agent/pull/301))
- Sampling support — server-initiated LLM requests ([#753](https://github.com/NousResearch/hermes-agent/pull/753))
- Resource and prompt discovery
- Automatic reconnection and security hardening
- Banner integration, `/reload-mcp` command
- `hermes tools` UI integration

### Browser
- Local browser backend — zero-cost headless Chromium (no Browserbase needed)
- Console/errors tool, annotated screenshots, auto-recording, dogfood QA skill ([#745](https://github.com/NousResearch/hermes-agent/pull/745))
- Screenshot sharing via MEDIA: on all messaging platforms ([#657](https://github.com/NousResearch/hermes-agent/pull/657))

### Terminal & Execution
- `execute_code` sandbox with json_parse, shell_quote, retry helpers
- Docker: custom volume mounts ([#158](https://github.com/NousResearch/hermes-agent/pull/158)) — @Indelwin
- Daytona cloud sandbox backend ([#451](https://github.com/NousResearch/hermes-agent/pull/451)) — @rovle
- SSH backend fix ([#59](https://github.com/NousResearch/hermes-agent/pull/59)) — @deankerr
- Shell noise filtering and login shell execution for environment consistency
- Head+tail truncation for execute_code stdout overflow
- Configurable background process notification modes

### File Operations
- Filesystem checkpoints and `/rollback` command ([#824](https://github.com/NousResearch/hermes-agent/pull/824))
- Structured tool result hints (next-action guidance) for patch and search_files ([#722](https://github.com/NousResearch/hermes-agent/issues/722))
- Docker volumes passed to sandbox container config ([#687](https://github.com/NousResearch/hermes-agent/pull/687)) — @manuelschipper

---

## 🧩 Skills Ecosystem

### Skills System
- Per-platform skill enable/disable ([#743](https://github.com/NousResearch/hermes-agent/pull/743)) — @teyrebaz33
- Conditional skill activation based on tool availability ([#785](https://github.com/NousResearch/hermes-agent/pull/785)) — @teyrebaz33
- Skill prerequisites — hide skills with unmet dependencies ([#659](https://github.com/NousResearch/hermes-agent/pull/659)) — @kshitijk4poor
- Optional skills — shipped but not activated by default
- `hermes skills browse` — paginated hub browsing
- Skills sub-category organization
- Platform-conditional skill loading
- Atomic skill file writes ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay
- Skills sync data loss prevention ([#563](https://github.com/NousResearch/hermes-agent/pull/563)) — @0xbyt4
- Dynamic skill slash commands for CLI and gateway

### New Skills (selected)
- **ASCII Art** — pyfiglet (571 fonts), cowsay, image-to-ascii ([#209](https://github.com/NousResearch/hermes-agent/pull/209)) — @0xbyt4
- **ASCII Video** — Full production pipeline ([#854](https://github.com/NousResearch/hermes-agent/pull/854)) — @SHL0MS
- **DuckDuckGo Search** — Firecrawl fallback ([#267](https://github.com/NousResearch/hermes-agent/pull/267)) — @gamedevCloudy; DDGS API expansion ([#598](https://github.com/NousResearch/hermes-agent/pull/598)) — @areu01or00
- **Solana Blockchain** — Wallet balances, USD pricing, token names ([#212](https://github.com/NousResearch/hermes-agent/pull/212)) — @gizdusum
- **AgentMail** — Agent-owned email inboxes ([#330](https://github.com/NousResearch/hermes-agent/pull/330)) — @teyrebaz33
- **Polymarket** — Prediction market data (read-only) ([#629](https://github.com/NousResearch/hermes-agent/pull/629))
- **OpenClaw Migration** — Official migration tool ([#570](https://github.com/NousResearch/hermes-agent/pull/570)) — @unmodeled-tyler
- **Domain Intelligence** — Passive recon: subdomains, SSL, WHOIS, DNS ([#136](https://github.com/NousResearch/hermes-agent/pull/136)) — @FurkanL0
- **Superpowers** — Software development skills ([#137](https://github.com/NousResearch/hermes-agent/pull/137)) — @kaos35
- **Hermes-Atropos** — RL environment development skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
- Plus: arXiv search, OCR/documents, Excalidraw diagrams, YouTube transcripts, GIF search, Pokémon player, Minecraft modpack server, OpenHue (Philips Hue), Google Workspace, Notion, PowerPoint, Obsidian, find-nearby, and 40+ MLOps skills

---

## 🔒 Security & Reliability

### Security Hardening
- Path traversal fix in skill_view — prevented reading arbitrary files ([#220](https://github.com/NousResearch/hermes-agent/issues/220)) — @Farukest
- Shell injection prevention in sudo password piping ([#65](https://github.com/NousResearch/hermes-agent/pull/65)) — @leonsgithub
- Dangerous command detection: multiline bypass fix ([#233](https://github.com/NousResearch/hermes-agent/pull/233)) — @Farukest; tee/process substitution patterns ([#280](https://github.com/NousResearch/hermes-agent/pull/280)) — @dogiladeveloper
- Symlink boundary check fix in skills_guard ([#386](https://github.com/NousResearch/hermes-agent/pull/386)) — @Farukest
- Symlink bypass fix in write deny list on macOS ([#61](https://github.com/NousResearch/hermes-agent/pull/61)) — @0xbyt4
- Multi-word prompt injection bypass prevention ([#192](https://github.com/NousResearch/hermes-agent/pull/192)) — @0xbyt4
- Cron prompt injection scanner bypass fix ([#63](https://github.com/NousResearch/hermes-agent/pull/63)) — @0xbyt4
- Enforce 0600/0700 file permissions on sensitive files ([#757](https://github.com/NousResearch/hermes-agent/pull/757))
- .env file permissions restricted to owner-only ([#529](https://github.com/NousResearch/hermes-agent/pull/529)) — @Himess
- `--force` flag properly blocked from overriding dangerous verdicts ([#388](https://github.com/NousResearch/hermes-agent/pull/388)) — @Farukest
- FTS5 query sanitization + DB connection leak fix ([#565](https://github.com/NousResearch/hermes-agent/pull/565)) — @0xbyt4
- Expand secret redaction patterns + config toggle to disable
- In-memory permanent allowlist to prevent data leak ([#600](https://github.com/NousResearch/hermes-agent/pull/600)) — @alireza78a

### Atomic Writes (data loss prevention)
- sessions.json ([#611](https://github.com/NousResearch/hermes-agent/pull/611)) — @alireza78a
- Cron jobs ([#146](https://github.com/NousResearch/hermes-agent/pull/146)) — @alireza78a
- .env config ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
- Process checkpoints ([#298](https://github.com/NousResearch/hermes-agent/pull/298)) — @aydnOktay
- Batch runner ([#297](https://github.com/NousResearch/hermes-agent/pull/297)) — @aydnOktay
- Skill files ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay

### Reliability
- Guard all print() against OSError for systemd/headless environments ([#963](https://github.com/NousResearch/hermes-agent/pull/963))
- Reset all retry counters at start of run_conversation ([#607](https://github.com/NousResearch/hermes-agent/pull/607)) — @0xbyt4
- Return deny on approval callback timeout instead of None ([#603](https://github.com/NousResearch/hermes-agent/pull/603)) — @0xbyt4
- Fix None message content crashes across codebase ([#277](https://github.com/NousResearch/hermes-agent/pull/277))
- Fix context overrun crash with local LLM backends ([#403](https://github.com/NousResearch/hermes-agent/pull/403)) — @ch3ronsa
- Prevent `_flush_sentinel` from leaking to external APIs ([#227](https://github.com/NousResearch/hermes-agent/pull/227)) — @Farukest
- Prevent conversation_history mutation in callers ([#229](https://github.com/NousResearch/hermes-agent/pull/229)) — @Farukest
- Fix systemd restart loop ([#614](https://github.com/NousResearch/hermes-agent/pull/614)) — @voidborne-d
- Close file handles and sockets to prevent fd leaks ([#568](https://github.com/NousResearch/hermes-agent/pull/568) — @alireza78a, [#296](https://github.com/NousResearch/hermes-agent/pull/296) — @alireza78a, [#709](https://github.com/NousResearch/hermes-agent/pull/709) — @memosr)
- Prevent data loss in clipboard PNG conversion ([#602](https://github.com/NousResearch/hermes-agent/pull/602)) — @0xbyt4
- Eliminate shell noise from terminal output ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4
- Timezone-aware now() for prompt, cron, and execute_code ([#309](https://github.com/NousResearch/hermes-agent/pull/309)) — @areu01or00

### Windows Compatibility
- Guard POSIX-only process functions ([#219](https://github.com/NousResearch/hermes-agent/pull/219)) — @Farukest
- Windows native support via Git Bash + ZIP-based update fallback
- pywinpty for PTY support ([#457](https://github.com/NousResearch/hermes-agent/pull/457)) — @shitcoinsherpa
- Explicit UTF-8 encoding on all config/data file I/O ([#458](https://github.com/NousResearch/hermes-agent/pull/458)) — @shitcoinsherpa
- Windows-compatible path handling ([#354](https://github.com/NousResearch/hermes-agent/pull/354), [#390](https://github.com/NousResearch/hermes-agent/pull/390)) — @Farukest
- Regex-based search output parsing for drive-letter paths ([#533](https://github.com/NousResearch/hermes-agent/pull/533)) — @Himess
- Auth store file lock for Windows ([#455](https://github.com/NousResearch/hermes-agent/pull/455)) — @shitcoinsherpa

---

## 🐛 Notable Bug Fixes

- Fix DeepSeek V3 tool call parser silently dropping multi-line JSON arguments ([#444](https://github.com/NousResearch/hermes-agent/pull/444)) — @PercyDikec
- Fix gateway transcript losing 1 message per turn due to offset mismatch ([#395](https://github.com/NousResearch/hermes-agent/pull/395)) — @PercyDikec
- Fix /retry command silently discarding the agent's final response ([#441](https://github.com/NousResearch/hermes-agent/pull/441)) — @PercyDikec
- Fix max-iterations retry returning empty string after think-block stripping ([#438](https://github.com/NousResearch/hermes-agent/pull/438)) — @PercyDikec
- Fix max-iterations retry using hardcoded max_tokens ([#436](https://github.com/NousResearch/hermes-agent/pull/436)) — @Farukest
- Fix Codex status dict key mismatch ([#448](https://github.com/NousResearch/hermes-agent/pull/448)) and visibility filter ([#446](https://github.com/NousResearch/hermes-agent/pull/446)) — @PercyDikec
- Strip \<think\> blocks from final user-facing responses ([#174](https://github.com/NousResearch/hermes-agent/pull/174)) — @Bartok9
- Fix \<think\> block regex stripping visible content when model discusses tags literally ([#786](https://github.com/NousResearch/hermes-agent/issues/786))
- Fix Mistral 422 errors from leftover finish_reason in assistant messages ([#253](https://github.com/NousResearch/hermes-agent/pull/253)) — @Sertug17
- Fix OPENROUTER_API_KEY resolution order across all code paths ([#295](https://github.com/NousResearch/hermes-agent/pull/295)) — @0xbyt4
- Fix OPENAI_BASE_URL API key priority ([#420](https://github.com/NousResearch/hermes-agent/pull/420)) — @manuelschipper
- Fix Anthropic "prompt is too long" 400 error not detected as context length error ([#813](https://github.com/NousResearch/hermes-agent/issues/813))
- Fix SQLite session transcript accumulating duplicate messages — 3-4x token inflation ([#860](https://github.com/NousResearch/hermes-agent/issues/860))
- Fix setup wizard skipping API key prompts on first install ([#748](https://github.com/NousResearch/hermes-agent/pull/748))
- Fix setup wizard showing OpenRouter model list for Nous Portal ([#575](https://github.com/NousResearch/hermes-agent/pull/575)) — @PercyDikec
- Fix provider selection not persisting when switching via hermes model ([#881](https://github.com/NousResearch/hermes-agent/pull/881))
- Fix Docker backend failing when docker not in PATH on macOS ([#889](https://github.com/NousResearch/hermes-agent/pull/889))
- Fix ClawHub Skills Hub adapter for API endpoint changes ([#286](https://github.com/NousResearch/hermes-agent/pull/286)) — @BP602
- Fix Honcho auto-enable when API key is present ([#243](https://github.com/NousResearch/hermes-agent/pull/243)) — @Bartok9
- Fix duplicate 'skills' subparser crash on Python 3.11+ ([#898](https://github.com/NousResearch/hermes-agent/issues/898))
- Fix memory tool entry parsing when content contains section sign ([#162](https://github.com/NousResearch/hermes-agent/pull/162)) — @aydnOktay
- Fix piped install silently aborting when interactive prompts fail ([#72](https://github.com/NousResearch/hermes-agent/pull/72)) — @cutepawss
- Fix false positives in recursive delete detection ([#68](https://github.com/NousResearch/hermes-agent/pull/68)) — @cutepawss
- Fix Ruff lint warnings across codebase ([#608](https://github.com/NousResearch/hermes-agent/pull/608)) — @JackTheGit
- Fix Anthropic native base URL fail-fast ([#173](https://github.com/NousResearch/hermes-agent/pull/173)) — @adavyas
- Fix install.sh creating ~/.hermes before moving Node.js directory ([#53](https://github.com/NousResearch/hermes-agent/pull/53)) — @JoshuaMart
- Fix SystemExit traceback during atexit cleanup on Ctrl+C ([#55](https://github.com/NousResearch/hermes-agent/pull/55)) — @bierlingm
- Restore missing MIT license file ([#620](https://github.com/NousResearch/hermes-agent/pull/620)) — @stablegenius49

---

## 🧪 Testing

- **3,289 tests** across agent, gateway, tools, cron, and CLI
- Parallelized test suite with pytest-xdist ([#802](https://github.com/NousResearch/hermes-agent/pull/802)) — @OutThisLife
- Unit tests batch 1: 8 core modules ([#60](https://github.com/NousResearch/hermes-agent/pull/60)) — @0xbyt4
- Unit tests batch 2: 8 more modules ([#62](https://github.com/NousResearch/hermes-agent/pull/62)) — @0xbyt4
- Unit tests batch 3: 8 untested modules ([#191](https://github.com/NousResearch/hermes-agent/pull/191)) — @0xbyt4
- Unit tests batch 4: 5 security/logic-critical modules ([#193](https://github.com/NousResearch/hermes-agent/pull/193)) — @0xbyt4
- AIAgent (run_agent.py) unit tests ([#67](https://github.com/NousResearch/hermes-agent/pull/67)) — @0xbyt4
- Trajectory compressor tests ([#203](https://github.com/NousResearch/hermes-agent/pull/203)) — @0xbyt4
- Clarify tool tests ([#121](https://github.com/NousResearch/hermes-agent/pull/121)) — @Bartok9
- Telegram format tests — 43 tests for italic/bold/code rendering ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4
- Vision tools type hints + 42 tests ([#792](https://github.com/NousResearch/hermes-agent/pull/792))
- Compressor tool-call boundary regression tests ([#648](https://github.com/NousResearch/hermes-agent/pull/648)) — @intertwine
- Test structure reorganization ([#34](https://github.com/NousResearch/hermes-agent/pull/34)) — @0xbyt4
- Shell noise elimination + fix 36 test failures ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4

---

## 🔬 RL & Evaluation Environments

- WebResearchEnv — Multi-step web research RL environment ([#434](https://github.com/NousResearch/hermes-agent/pull/434)) — @jackx707
- Modal sandbox concurrency limits to avoid deadlocks ([#621](https://github.com/NousResearch/hermes-agent/pull/621)) — @voteblake
- Hermes-atropos-environments bundled skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
- Local vLLM instance support for evaluation — @dmahan93
- YC-Bench long-horizon agent benchmark environment
- OpenThoughts-TBLite evaluation environment and scripts

---

## 📚 Documentation

- Full documentation website (Docusaurus) with 37+ pages
- Comprehensive platform setup guides for Telegram, Discord, Slack, WhatsApp, Signal, Email
- AGENTS.md — development guide for AI coding assistants
- CONTRIBUTING.md ([#117](https://github.com/NousResearch/hermes-agent/pull/117)) — @Bartok9
- Slash commands reference ([#142](https://github.com/NousResearch/hermes-agent/pull/142)) — @Bartok9
- Comprehensive AGENTS.md accuracy audit ([#732](https://github.com/NousResearch/hermes-agent/pull/732))
- Skin/theme system documentation
- MCP documentation and examples
- Docs accuracy audit — 35+ corrections
- Documentation typo fixes ([#825](https://github.com/NousResearch/hermes-agent/pull/825), [#439](https://github.com/NousResearch/hermes-agent/pull/439)) — @JackTheGit
- CLI config precedence and terminology standardization ([#166](https://github.com/NousResearch/hermes-agent/pull/166), [#167](https://github.com/NousResearch/hermes-agent/pull/167), [#168](https://github.com/NousResearch/hermes-agent/pull/168)) — @Jr-kenny
- Telegram token regex documentation ([#713](https://github.com/NousResearch/hermes-agent/pull/713)) — @VolodymyrBg

---

## 👥 Contributors

Thank you to the 63 contributors who made this release possible! In just over two weeks, the Hermes Agent community came together to ship an extraordinary amount of work.

### Core
- **@teknium1** — 43 PRs: Project lead, core architecture, provider router, sessions, skills, CLI, documentation

### Top Community Contributors
- **@0xbyt4** — 40 PRs: MCP client, Home Assistant, security fixes (symlink, prompt injection, cron), extensive test coverage (6 batches), ascii-art skill, shell noise elimination, skills sync, Telegram formatting, and dozens more
- **@Farukest** — 16 PRs: Security hardening (path traversal, dangerous command detection, symlink boundary), Windows compatibility (POSIX guards, path handling), WhatsApp fixes, max-iterations retry, gateway fixes
- **@aydnOktay** — 11 PRs: Atomic writes (process checkpoints, batch runner, skill files), error handling improvements across Telegram, Discord, code execution, transcription, TTS, and skills
- **@Bartok9** — 9 PRs: CONTRIBUTING.md, slash commands reference, Discord channel topics, think-block stripping, TTS fix, Honcho fix, session count fix, clarify tests
- **@PercyDikec** — 7 PRs: DeepSeek V3 parser fix, /retry response discard, gateway transcript offset, Codex status/visibility, max-iterations retry, setup wizard fix
- **@teyrebaz33** — 5 PRs: Skills enable/disable system, quick commands, personality customization, conditional skill activation
- **@alireza78a** — 5 PRs: Atomic writes (cron, sessions), fd leak prevention, security allowlist, code execution socket cleanup
- **@shitcoinsherpa** — 3 PRs: Windows support (pywinpty, UTF-8 encoding, auth store lock)
- **@Himess** — 3 PRs: Cron/HomeAssistant/Daytona fix, Windows drive-letter parsing, .env permissions
- **@satelerd** — 2 PRs: WhatsApp native media, multi-user session isolation
- **@rovle** — 1 PR: Daytona cloud sandbox backend (4 commits)
- **@erosika** — 1 PR: Honcho AI-native memory integration
- **@dmahan93** — 1 PR: --fuck-it-ship-it flag + RL environment work
- **@SHL0MS** — 1 PR: ASCII video skill

### All Contributors
@0xbyt4, @BP602, @Bartok9, @Farukest, @FurkanL0, @Himess, @Indelwin, @JackTheGit, @JoshuaMart, @Jr-kenny, @OutThisLife, @PercyDikec, @SHL0MS, @Sertug17, @VencentSoliman, @VolodymyrBg, @adavyas, @alireza78a, @areu01or00, @aydnOktay, @batuhankocyigit, @bierlingm, @caentzminger, @cesareth, @ch3ronsa, @christomitov, @cutepawss, @deankerr, @dmahan93, @dogiladeveloper, @dragonkhoi, @erosika, @gamedevCloudy, @gizdusum, @grp06, @intertwine, @jackx707, @jdblackstar, @johnh4098, @kaos35, @kshitijk4poor, @leonsgithub, @luisv-1, @manuelschipper, @mehmetkr-31, @memosr, @PeterFile, @rewbs, @rovle, @rsavitt, @satelerd, @spanishflu-est1918, @stablegenius49, @tars90percent, @tekelala, @teknium1, @teyrebaz33, @tripledoublev, @unmodeled-tyler, @voidborne-d, @voteblake, @ygd58

---

**Full Changelog**: [v0.1.0...v2026.3.12](https://github.com/NousResearch/hermes-agent/compare/v0.1.0...v2026.3.12)


================================================
FILE: RELEASE_v0.3.0.md
================================================
# Hermes Agent v0.3.0 (v2026.3.17)

**Release Date:** March 17, 2026

> The streaming, plugins, and provider release — unified real-time token delivery, first-class plugin architecture, rebuilt provider system with Vercel AI Gateway, native Anthropic provider, smart approvals, live Chrome CDP browser connect, ACP IDE integration, Honcho memory, voice mode, persistent shell, and 50+ bug fixes across every platform.

---

## ✨ Highlights

- **Unified Streaming Infrastructure** — Real-time token-by-token delivery in CLI and all gateway platforms. Responses stream as they're generated instead of arriving as a block. ([#1538](https://github.com/NousResearch/hermes-agent/pull/1538))

- **First-Class Plugin Architecture** — Drop Python files into `~/.hermes/plugins/` to extend Hermes with custom tools, commands, and hooks. No forking required. ([#1544](https://github.com/NousResearch/hermes-agent/pull/1544), [#1555](https://github.com/NousResearch/hermes-agent/pull/1555))

- **Native Anthropic Provider** — Direct Anthropic API calls with Claude Code credential auto-discovery, OAuth PKCE flows, and native prompt caching. No OpenRouter middleman needed. ([#1097](https://github.com/NousResearch/hermes-agent/pull/1097))

- **Smart Approvals + /stop Command** — Codex-inspired approval system that learns which commands are safe and remembers your preferences. `/stop` kills the current agent run immediately. ([#1543](https://github.com/NousResearch/hermes-agent/pull/1543))

- **Honcho Memory Integration** — Async memory writes, configurable recall modes, session title integration, and multi-user isolation in gateway mode. By @erosika. ([#736](https://github.com/NousResearch/hermes-agent/pull/736))

- **Voice Mode** — Push-to-talk in CLI, voice notes in Telegram/Discord, Discord voice channel support, and local Whisper transcription via faster-whisper. ([#1299](https://github.com/NousResearch/hermes-agent/pull/1299), [#1185](https://github.com/NousResearch/hermes-agent/pull/1185), [#1429](https://github.com/NousResearch/hermes-agent/pull/1429))

- **Concurrent Tool Execution** — Multiple independent tool calls now run in parallel via ThreadPoolExecutor, significantly reducing latency for multi-tool turns. ([#1152](https://github.com/NousResearch/hermes-agent/pull/1152))

- **PII Redaction** — When `privacy.redact_pii` is enabled, personally identifiable information is automatically scrubbed before sending context to LLM providers. ([#1542](https://github.com/NousResearch/hermes-agent/pull/1542))

- **`/browser connect` via CDP** — Attach browser tools to a live Chrome instance through Chrome DevTools Protocol. Debug, inspect, and interact with pages you already have open. ([#1549](https://github.com/NousResearch/hermes-agent/pull/1549))

- **Vercel AI Gateway Provider** — Route Hermes through Vercel's AI Gateway for access to their model catalog and infrastructure. ([#1628](https://github.com/NousResearch/hermes-agent/pull/1628))

- **Centralized Provider Router** — Rebuilt provider system with `call_llm` API, unified `/model` command, auto-detect provider on model switch, and direct endpoint overrides for auxiliary/delegation clients. ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003), [#1506](https://github.com/NousResearch/hermes-agent/pull/1506), [#1375](https://github.com/NousResearch/hermes-agent/pull/1375))

- **ACP Server (IDE Integration)** — VS Code, Zed, and JetBrains can now connect to Hermes as an agent backend, with full slash command support. ([#1254](https://github.com/NousResearch/hermes-agent/pull/1254), [#1532](https://github.com/NousResearch/hermes-agent/pull/1532))

- **Persistent Shell Mode** — Local and SSH terminal backends can maintain shell state across tool calls — cd, env vars, and aliases persist. By @alt-glitch. ([#1067](https://github.com/NousResearch/hermes-agent/pull/1067), [#1483](https://github.com/NousResearch/hermes-agent/pull/1483))

- **Agentic On-Policy Distillation (OPD)** — New RL training environment for distilling agent policies, expanding the Atropos training ecosystem. ([#1149](https://github.com/NousResearch/hermes-agent/pull/1149))

---

## 🏗️ Core Agent & Architecture

### Provider & Model Support
- **Centralized provider router** with `call_llm` API and unified `/model` command — switch models and providers seamlessly ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
- **Vercel AI Gateway** provider support ([#1628](https://github.com/NousResearch/hermes-agent/pull/1628))
- **Auto-detect provider** when switching models via `/model` ([#1506](https://github.com/NousResearch/hermes-agent/pull/1506))
- **Direct endpoint overrides** for auxiliary and delegation clients — point vision/subagent calls at specific endpoints ([#1375](https://github.com/NousResearch/hermes-agent/pull/1375))
- **Native Anthropic auxiliary vision** — use Claude's native vision API instead of routing through OpenAI-compatible endpoints ([#1377](https://github.com/NousResearch/hermes-agent/pull/1377))
- Anthropic OAuth flow improvements — auto-run `claude setup-token`, reauthentication, PKCE state persistence, identity fingerprinting ([#1132](https://github.com/NousResearch/hermes-agent/pull/1132), [#1360](https://github.com/NousResearch/hermes-agent/pull/1360), [#1396](https://github.com/NousResearch/hermes-agent/pull/1396), [#1597](https://github.com/NousResearch/hermes-agent/pull/1597))
- Fix adaptive thinking without `budget_tokens` for Claude 4.6 models — by @ASRagab ([#1128](https://github.com/NousResearch/hermes-agent/pull/1128))
- Fix Anthropic cache markers through adapter — by @brandtcormorant ([#1216](https://github.com/NousResearch/hermes-agent/pull/1216))
- Retry Anthropic 429/529 errors and surface details to users — by @0xbyt4 ([#1585](https://github.com/NousResearch/hermes-agent/pull/1585))
- Fix Anthropic adapter max_tokens, fallback crash, proxy base_url — by @0xbyt4 ([#1121](https://github.com/NousResearch/hermes-agent/pull/1121))
- Fix DeepSeek V3 parser dropping multiple parallel tool calls — by @mr-emmett-one ([#1365](https://github.com/NousResearch/hermes-agent/pull/1365), [#1300](https://github.com/NousResearch/hermes-agent/pull/1300))
- Accept unlisted models with warning instead of rejecting ([#1047](https://github.com/NousResearch/hermes-agent/pull/1047), [#1102](https://github.com/NousResearch/hermes-agent/pull/1102))
- Skip reasoning params for unsupported OpenRouter models ([#1485](https://github.com/NousResearch/hermes-agent/pull/1485))
- MiniMax Anthropic API compatibility fix ([#1623](https://github.com/NousResearch/hermes-agent/pull/1623))
- Custom endpoint `/models` verification and `/v1` base URL suggestion ([#1480](https://github.com/NousResearch/hermes-agent/pull/1480))
- Resolve delegation providers from `custom_providers` config ([#1328](https://github.com/NousResearch/hermes-agent/pull/1328))
- Kimi model additions and User-Agent fix ([#1039](https://github.com/NousResearch/hermes-agent/pull/1039))
- Strip `call_id`/`response_item_id` for Mistral compatibility ([#1058](https://github.com/NousResearch/hermes-agent/pull/1058))

### Agent Loop & Conversation
- **Anthropic Context Editing API** support ([#1147](https://github.com/NousResearch/hermes-agent/pull/1147))
- Improved context compaction handoff summaries — compressor now preserves more actionable state ([#1273](https://github.com/NousResearch/hermes-agent/pull/1273))
- Sync session_id after mid-run context compression ([#1160](https://github.com/NousResearch/hermes-agent/pull/1160))
- Session hygiene threshold tuned to 50% for more proactive compression ([#1096](https://github.com/NousResearch/hermes-agent/pull/1096), [#1161](https://github.com/NousResearch/hermes-agent/pull/1161))
- Include session ID in system prompt via `--pass-session-id` flag ([#1040](https://github.com/NousResearch/hermes-agent/pull/1040))
- Prevent closed OpenAI client reuse across retries ([#1391](https://github.com/NousResearch/hermes-agent/pull/1391))
- Sanitize chat payloads and provider precedence ([#1253](https://github.com/NousResearch/hermes-agent/pull/1253))
- Handle dict tool call arguments from Codex and local backends ([#1393](https://github.com/NousResearch/hermes-agent/pull/1393), [#1440](https://github.com/NousResearch/hermes-agent/pull/1440))

### Memory & Sessions
- **Improve memory prioritization** — user preferences and corrections weighted above procedural knowledge ([#1548](https://github.com/NousResearch/hermes-agent/pull/1548))
- Tighter memory and session recall guidance in system prompts ([#1329](https://github.com/NousResearch/hermes-agent/pull/1329))
- Persist CLI token counts to session DB for `/insights` ([#1498](https://github.com/NousResearch/hermes-agent/pull/1498))
- Keep Honcho recall out of the cached system prefix ([#1201](https://github.com/NousResearch/hermes-agent/pull/1201))
- Correct `seed_ai_identity` to use `session.add_messages()` ([#1475](https://github.com/NousResearch/hermes-agent/pull/1475))
- Isolate Honcho session routing for multi-user gateway ([#1500](https://github.com/NousResearch/hermes-agent/pull/1500))

---

## 📱 Messaging Platforms (Gateway)

### Gateway Core
- **System gateway service mode** — run as a system-level systemd service, not just user-level ([#1371](https://github.com/NousResearch/hermes-agent/pull/1371))
- **Gateway install scope prompts** — choose user vs system scope during setup ([#1374](https://github.com/NousResearch/hermes-agent/pull/1374))
- **Reasoning hot reload** — change reasoning settings without restarting the gateway ([#1275](https://github.com/NousResearch/hermes-agent/pull/1275))
- Default group sessions to per-user isolation — no more shared state across users in group chats ([#1495](https://github.com/NousResearch/hermes-agent/pull/1495), [#1417](https://github.com/NousResearch/hermes-agent/pull/1417))
- Harden gateway restart recovery ([#1310](https://github.com/NousResearch/hermes-agent/pull/1310))
- Cancel active runs during shutdown ([#1427](https://github.com/NousResearch/hermes-agent/pull/1427))
- SSL certificate auto-detection for NixOS and non-standard systems ([#1494](https://github.com/NousResearch/hermes-agent/pull/1494))
- Auto-detect D-Bus session bus for `systemctl --user` on headless servers ([#1601](https://github.com/NousResearch/hermes-agent/pull/1601))
- Auto-enable systemd linger during gateway install on headless servers ([#1334](https://github.com/NousResearch/hermes-agent/pull/1334))
- Fall back to module entrypoint when `hermes` is not on PATH ([#1355](https://github.com/NousResearch/hermes-agent/pull/1355))
- Fix dual gateways on macOS launchd after `hermes update` ([#1567](https://github.com/NousResearch/hermes-agent/pull/1567))
- Remove recursive ExecStop from systemd units ([#1530](https://github.com/NousResearch/hermes-agent/pull/1530))
- Prevent logging handler accumulation in gateway mode ([#1251](https://github.com/NousResearch/hermes-agent/pull/1251))
- Restart on retryable startup failures — by @jplew ([#1517](https://github.com/NousResearch/hermes-agent/pull/1517))
- Backfill model on gateway sessions after agent runs ([#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
- PID-based gateway kill and deferred config write ([#1499](https://github.com/NousResearch/hermes-agent/pull/1499))

### Telegram
- Buffer media groups to prevent self-interruption from photo bursts ([#1341](https://github.com/NousResearch/hermes-agent/pull/1341), [#1422](https://github.com/NousResearch/hermes-agent/pull/1422))
- Retry on transient TLS failures during connect and send ([#1535](https://github.com/NousResearch/hermes-agent/pull/1535))
- Harden polling conflict handling ([#1339](https://github.com/NousResearch/hermes-agent/pull/1339))
- Escape chunk indicators and inline code in MarkdownV2 ([#1478](https://github.com/NousResearch/hermes-agent/pull/1478), [#1626](https://github.com/NousResearch/hermes-agent/pull/1626))
- Check updater/app state before disconnect ([#1389](https://github.com/NousResearch/hermes-agent/pull/1389))

### Discord
- `/thread` command with `auto_thread` config and media metadata fixes ([#1178](https://github.com/NousResearch/hermes-agent/pull/1178))
- Auto-thread on @mention, skip mention text in bot threads ([#1438](https://github.com/NousResearch/hermes-agent/pull/1438))
- Retry without reply reference for system messages ([#1385](https://github.com/NousResearch/hermes-agent/pull/1385))
- Preserve native document and video attachment support ([#1392](https://github.com/NousResearch/hermes-agent/pull/1392))
- Defer discord adapter annotations to avoid optional import crashes ([#1314](https://github.com/NousResearch/hermes-agent/pull/1314))

### Slack
- Thread handling overhaul — progress messages, responses, and session isolation all respect threads ([#1103](https://github.com/NousResearch/hermes-agent/pull/1103))
- Formatting, reactions, user resolution, and command improvements ([#1106](https://github.com/NousResearch/hermes-agent/pull/1106))
- Fix MAX_MESSAGE_LENGTH 3900 → 39000 ([#1117](https://github.com/NousResearch/hermes-agent/pull/1117))
- File upload fallback preserves thread context — by @0xbyt4 ([#1122](https://github.com/NousResearch/hermes-agent/pull/1122))
- Improve setup guidance ([#1387](https://github.com/NousResearch/hermes-agent/pull/1387))

### Email
- Fix IMAP UID tracking and SMTP TLS verification ([#1305](https://github.com/NousResearch/hermes-agent/pull/1305))
- Add `skip_attachments` option via config.yaml ([#1536](https://github.com/NousResearch/hermes-agent/pull/1536))

### Home Assistant
- Event filtering closed by default ([#1169](https://github.com/NousResearch/hermes-agent/pull/1169))

---

## 🖥️ CLI & User Experience

### Interactive CLI
- **Persistent CLI status bar** — always-visible model, provider, and token counts ([#1522](https://github.com/NousResearch/hermes-agent/pull/1522))
- **File path autocomplete** in the input prompt ([#1545](https://github.com/NousResearch/hermes-agent/pull/1545))
- **`/plan` command** — generate implementation plans from specs ([#1372](https://github.com/NousResearch/hermes-agent/pull/1372), [#1381](https://github.com/NousResearch/hermes-agent/pull/1381))
- **Major `/rollback` improvements** — richer checkpoint history, clearer UX ([#1505](https://github.com/NousResearch/hermes-agent/pull/1505))
- **Preload CLI skills on launch** — skills are ready before the first prompt ([#1359](https://github.com/NousResearch/hermes-agent/pull/1359))
- **Centralized slash command registry** — all commands defined once, consumed everywhere ([#1603](https://github.com/NousResearch/hermes-agent/pull/1603))
- `/bg` alias for `/background` ([#1590](https://github.com/NousResearch/hermes-agent/pull/1590))
- Prefix matching for slash commands — `/mod` resolves to `/model` ([#1320](https://github.com/NousResearch/hermes-agent/pull/1320))
- `/new`, `/reset`, `/clear` now start genuinely fresh sessions ([#1237](https://github.com/NousResearch/hermes-agent/pull/1237))
- Accept session ID prefixes for session actions ([#1425](https://github.com/NousResearch/hermes-agent/pull/1425))
- TUI prompt and accent output now respect active skin ([#1282](https://github.com/NousResearch/hermes-agent/pull/1282))
- Centralize tool emoji metadata in registry + skin integration ([#1484](https://github.com/NousResearch/hermes-agent/pull/1484))
- "View full command" option added to dangerous command approval — by @teknium1 based on design by community ([#887](https://github.com/NousResearch/hermes-agent/pull/887))
- Non-blocking startup update check and banner deduplication ([#1386](https://github.com/NousResearch/hermes-agent/pull/1386))
- `/reasoning` command output ordering and inline think extraction fixes ([#1031](https://github.com/NousResearch/hermes-agent/pull/1031))
- Verbose mode shows full untruncated output ([#1472](https://github.com/NousResearch/hermes-agent/pull/1472))
- Fix `/status` to report live state and tokens ([#1476](https://github.com/NousResearch/hermes-agent/pull/1476))
- Seed a default global SOUL.md ([#1311](https://github.com/NousResearch/hermes-agent/pull/1311))

### Setup & Configuration
- **OpenClaw migration** during first-time setup — by @kshitijk4poor ([#981](https://github.com/NousResearch/hermes-agent/pull/981))
- `hermes claw migrate` command + migration docs ([#1059](https://github.com/NousResearch/hermes-agent/pull/1059))
- Smart vision setup that respects the user's chosen provider ([#1323](https://github.com/NousResearch/hermes-agent/pull/1323))
- Handle headless setup flows end-to-end ([#1274](https://github.com/NousResearch/hermes-agent/pull/1274))
- Prefer curses over `simple_term_menu` in setup.py ([#1487](https://github.com/NousResearch/hermes-agent/pull/1487))
- Show effective model and provider in `/status` ([#1284](https://github.com/NousResearch/hermes-agent/pull/1284))
- Config set examples use placeholder syntax ([#1322](https://github.com/NousResearch/hermes-agent/pull/1322))
- Reload .env over stale shell overrides ([#1434](https://github.com/NousResearch/hermes-agent/pull/1434))
- Fix is_coding_plan NameError crash — by @0xbyt4 ([#1123](https://github.com/NousResearch/hermes-agent/pull/1123))
- Add missing packages to setuptools config — by @alt-glitch ([#912](https://github.com/NousResearch/hermes-agent/pull/912))
- Installer: clarify why sudo is needed at every prompt ([#1602](https://github.com/NousResearch/hermes-agent/pull/1602))

---

## 🔧 Tool System

### Terminal & Execution
- **Persistent shell mode** for local and SSH backends — maintain shell state across tool calls — by @alt-glitch ([#1067](https://github.com/NousResearch/hermes-agent/pull/1067), [#1483](https://github.com/NousResearch/hermes-agent/pull/1483))
- **Tirith pre-exec command scanning** — security layer that analyzes commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
- Strip Hermes provider env vars from all subprocess environments ([#1157](https://github.com/NousResearch/hermes-agent/pull/1157), [#1172](https://github.com/NousResearch/hermes-agent/pull/1172), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399), [#1419](https://github.com/NousResearch/hermes-agent/pull/1419)) — initial fix by @eren-karakus0
- SSH preflight check ([#1486](https://github.com/NousResearch/hermes-agent/pull/1486))
- Docker backend: make cwd workspace mount explicit opt-in ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
- Add project root to PYTHONPATH in execute_code sandbox ([#1383](https://github.com/NousResearch/hermes-agent/pull/1383))
- Eliminate execute_code progress spam on gateway platforms ([#1098](https://github.com/NousResearch/hermes-agent/pull/1098))
- Clearer docker backend preflight errors ([#1276](https://github.com/NousResearch/hermes-agent/pull/1276))

### Browser
- **`/browser connect`** — attach browser tools to a live Chrome instance via CDP ([#1549](https://github.com/NousResearch/hermes-agent/pull/1549))
- Improve browser cleanup, local browser PATH setup, and screenshot recovery ([#1333](https://github.com/NousResearch/hermes-agent/pull/1333))

### MCP
- **Selective tool loading** with utility policies — filter which MCP tools are available ([#1302](https://github.com/NousResearch/hermes-agent/pull/1302))
- Auto-reload MCP tools when `mcp_servers` config changes without restart ([#1474](https://github.com/NousResearch/hermes-agent/pull/1474))
- Resolve npx stdio connection failures ([#1291](https://github.com/NousResearch/hermes-agent/pull/1291))
- Preserve MCP toolsets when saving platform tool config ([#1421](https://github.com/NousResearch/hermes-agent/pull/1421))

### Vision
- Unify vision backend gating ([#1367](https://github.com/NousResearch/hermes-agent/pull/1367))
- Surface actual error reason instead of generic message ([#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
- Make Claude image handling work end-to-end ([#1408](https://github.com/NousResearch/hermes-agent/pull/1408))

### Cron
- **Compress cron management into one tool** — single `cronjob` tool replaces multiple commands ([#1343](https://github.com/NousResearch/hermes-agent/pull/1343))
- Suppress duplicate cron sends to auto-delivery targets ([#1357](https://github.com/NousResearch/hermes-agent/pull/1357))
- Persist cron sessions to SQLite ([#1255](https://github.com/NousResearch/hermes-agent/pull/1255))
- Per-job runtime overrides (provider, model, base_url) ([#1398](https://github.com/NousResearch/hermes-agent/pull/1398))
- Atomic write in `save_job_output` to prevent data loss on crash ([#1173](https://github.com/NousResearch/hermes-agent/pull/1173))
- Preserve thread context for `deliver=origin` ([#1437](https://github.com/NousResearch/hermes-agent/pull/1437))

### Patch Tool
- Avoid corrupting pipe chars in V4A patch apply ([#1286](https://github.com/NousResearch/hermes-agent/pull/1286))
- Permissive `block_anchor` thresholds and unicode normalization ([#1539](https://github.com/NousResearch/hermes-agent/pull/1539))

### Delegation
- Add observability metadata to subagent results (model, tokens, duration, tool trace) ([#1175](https://github.com/NousResearch/hermes-agent/pull/1175))

---

## 🧩 Skills Ecosystem

### Skills System
- **Integrate skills.sh** as a hub source alongside ClawHub ([#1303](https://github.com/NousResearch/hermes-agent/pull/1303))
- Secure skill env setup on load ([#1153](https://github.com/NousResearch/hermes-agent/pull/1153))
- Honor policy table for dangerous verdicts ([#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
- Harden ClawHub skill search exact matches ([#1400](https://github.com/NousResearch/hermes-agent/pull/1400))
- Fix ClawHub skill install — use `/download` ZIP endpoint ([#1060](https://github.com/NousResearch/hermes-agent/pull/1060))
- Avoid mislabeling local skills as builtin — by @arceus77-7 ([#862](https://github.com/NousResearch/hermes-agent/pull/862))

### New Skills
- **Linear** project management ([#1230](https://github.com/NousResearch/hermes-agent/pull/1230))
- **X/Twitter** via x-cli ([#1285](https://github.com/NousResearch/hermes-agent/pull/1285))
- **Telephony** — Twilio, SMS, and AI calls ([#1289](https://github.com/NousResearch/hermes-agent/pull/1289))
- **1Password** — by @arceus77-7 ([#883](https://github.com/NousResearch/hermes-agent/pull/883), [#1179](https://github.com/NousResearch/hermes-agent/pull/1179))
- **NeuroSkill BCI** integration ([#1135](https://github.com/NousResearch/hermes-agent/pull/1135))
- **Blender MCP** for 3D modeling ([#1531](https://github.com/NousResearch/hermes-agent/pull/1531))
- **OSS Security Forensics** ([#1482](https://github.com/NousResearch/hermes-agent/pull/1482))
- **Parallel CLI** research skill ([#1301](https://github.com/NousResearch/hermes-agent/pull/1301))
- **OpenCode** CLI skill ([#1174](https://github.com/NousResearch/hermes-agent/pull/1174))
- **ASCII Video** skill refactored — by @SHL0MS ([#1213](https://github.com/NousResearch/hermes-agent/pull/1213), [#1598](https://github.com/NousResearch/hermes-agent/pull/1598))

---

## 🎙️ Voice Mode

- Voice mode foundation — push-to-talk CLI, Telegram/Discord voice notes ([#1299](https://github.com/NousResearch/hermes-agent/pull/1299))
- Free local Whisper transcription via faster-whisper ([#1185](https://github.com/NousResearch/hermes-agent/pull/1185))
- Discord voice channel reliability fixes ([#1429](https://github.com/NousResearch/hermes-agent/pull/1429))
- Restore local STT fallback for gateway voice notes ([#1490](https://github.com/NousResearch/hermes-agent/pull/1490))
- Honor `stt.enabled: false` across gateway transcription ([#1394](https://github.com/NousResearch/hermes-agent/pull/1394))
- Fix bogus incapability message on Telegram voice notes (Issue [#1033](https://github.com/NousResearch/hermes-agent/issues/1033))

---

## 🔌 ACP (IDE Integration)

- Restore ACP server implementation ([#1254](https://github.com/NousResearch/hermes-agent/pull/1254))
- Support slash commands in ACP adapter ([#1532](https://github.com/NousResearch/hermes-agent/pull/1532))

---

## 🧪 RL Training

- **Agentic On-Policy Distillation (OPD)** environment — new RL training environment for agent policy distillation ([#1149](https://github.com/NousResearch/hermes-agent/pull/1149))
- Make tinker-atropos RL training fully optional ([#1062](https://github.com/NousResearch/hermes-agent/pull/1062))

---

## 🔒 Security & Reliability

### Security Hardening
- **Tirith pre-exec command scanning** — static analysis of terminal commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
- **PII redaction** when `privacy.redact_pii` is enabled ([#1542](https://github.com/NousResearch/hermes-agent/pull/1542))
- Strip Hermes provider/gateway/tool env vars from all subprocess environments ([#1157](https://github.com/NousResearch/hermes-agent/pull/1157), [#1172](https://github.com/NousResearch/hermes-agent/pull/1172), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399), [#1419](https://github.com/NousResearch/hermes-agent/pull/1419))
- Docker cwd workspace mount now explicit opt-in — never auto-mount host directories ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
- Escape parens and braces in fork bomb regex pattern ([#1397](https://github.com/NousResearch/hermes-agent/pull/1397))
- Harden `.worktreeinclude` path containment ([#1388](https://github.com/NousResearch/hermes-agent/pull/1388))
- Use description as `pattern_key` to prevent approval collisions ([#1395](https://github.com/NousResearch/hermes-agent/pull/1395))

### Reliability
- Guard init-time stdio writes ([#1271](https://github.com/NousResearch/hermes-agent/pull/1271))
- Session log writes reuse shared atomic JSON helper ([#1280](https://github.com/NousResearch/hermes-agent/pull/1280))
- Atomic temp cleanup protected on interrupts ([#1401](https://github.com/NousResearch/hermes-agent/pull/1401))

---

## 🐛 Notable Bug Fixes

- **`/status` always showing 0 tokens** — now reports live state (Issue [#1465](https://github.com/NousResearch/hermes-agent/issues/1465), [#1476](https://github.com/NousResearch/hermes-agent/pull/1476))
- **Custom model endpoints not working** — restored config-saved endpoint resolution (Issue [#1460](https://github.com/NousResearch/hermes-agent/issues/1460), [#1373](https://github.com/NousResearch/hermes-agent/pull/1373))
- **MCP tools not visible until restart** — auto-reload on config change (Issue [#1036](https://github.com/NousResearch/hermes-agent/issues/1036), [#1474](https://github.com/NousResearch/hermes-agent/pull/1474))
- **`hermes tools` removing MCP tools** — preserve MCP toolsets when saving (Issue [#1247](https://github.com/NousResearch/hermes-agent/issues/1247), [#1421](https://github.com/NousResearch/hermes-agent/pull/1421))
- **Terminal subprocesses inheriting `OPENAI_BASE_URL`** breaking external tools (Issue [#1002](https://github.com/NousResearch/hermes-agent/issues/1002), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399))
- **Background process lost on gateway restart** — improved recovery (Issue [#1144](https://github.com/NousResearch/hermes-agent/issues/1144))
- **Cron jobs not persisting state** — now stored in SQLite (Issue [#1416](https://github.com/NousResearch/hermes-agent/issues/1416), [#1255](https://github.com/NousResearch/hermes-agent/pull/1255))
- **Cronjob `deliver: origin` not preserving thread context** (Issue [#1219](https://github.com/NousResearch/hermes-agent/issues/1219), [#1437](https://github.com/NousResearch/hermes-agent/pull/1437))
- **Gateway systemd service failing to auto-restart** when browser processes orphaned (Issue [#1617](https://github.com/NousResearch/hermes-agent/issues/1617))
- **`/background` completion report cut off in Telegram** (Issue [#1443](https://github.com/NousResearch/hermes-agent/issues/1443))
- **Model switching not taking effect** (Issue [#1244](https://github.com/NousResearch/hermes-agent/issues/1244), [#1183](https://github.com/NousResearch/hermes-agent/pull/1183))
- **`hermes doctor` reporting cronjob as unavailable** (Issue [#878](https://github.com/NousResearch/hermes-agent/issues/878), [#1180](https://github.com/NousResearch/hermes-agent/pull/1180))
- **WhatsApp bridge messages not received** from mobile (Issue [#1142](https://github.com/NousResearch/hermes-agent/issues/1142))
- **Setup wizard hanging on headless SSH** (Issue [#905](https://github.com/NousResearch/hermes-agent/issues/905), [#1274](https://github.com/NousResearch/hermes-agent/pull/1274))
- **Log handler accumulation** degrading gateway performance (Issue [#990](https://github.com/NousResearch/hermes-agent/issues/990), [#1251](https://github.com/NousResearch/hermes-agent/pull/1251))
- **Gateway NULL model in DB** (Issue [#987](https://github.com/NousResearch/hermes-agent/issues/987), [#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
- **Strict endpoints rejecting replayed tool_calls** (Issue [#893](https://github.com/NousResearch/hermes-agent/issues/893))
- **Remaining hardcoded `~/.hermes` paths** — all now respect `HERMES_HOME` (Issue [#892](https://github.com/NousResearch/hermes-agent/issues/892), [#1233](https://github.com/NousResearch/hermes-agent/pull/1233))
- **Delegate tool not working with custom inference providers** (Issue [#1011](https://github.com/NousResearch/hermes-agent/issues/1011), [#1328](https://github.com/NousResearch/hermes-agent/pull/1328))
- **Skills Guard blocking official skills** (Issue [#1006](https://github.com/NousResearch/hermes-agent/issues/1006), [#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
- **Setup writing provider before model selection** (Issue [#1182](https://github.com/NousResearch/hermes-agent/issues/1182))
- **`GatewayConfig.get()` AttributeError** crashing all message handling (Issue [#1158](https://github.com/NousResearch/hermes-agent/issues/1158), [#1287](https://github.com/NousResearch/hermes-agent/pull/1287))
- **`/update` hard-failing with "command not found"** (Issue [#1049](https://github.com/NousResearch/hermes-agent/issues/1049))
- **Image analysis failing silently** (Issue [#1034](https://github.com/NousResearch/hermes-agent/issues/1034), [#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
- **API `BadRequestError` from `'dict'` object has no attribute `'strip'`** (Issue [#1071](https://github.com/NousResearch/hermes-agent/issues/1071))
- **Slash commands requiring exact full name** — now uses prefix matching (Issue [#928](https://github.com/NousResearch/hermes-agent/issues/928), [#1320](https://github.com/NousResearch/hermes-agent/pull/1320))
- **Gateway stops responding when terminal is closed on headless** (Issue [#1005](https://github.com/NousResearch/hermes-agent/issues/1005))

---

## 🧪 Testing

- Cover empty cached Anthropic tool-call turns ([#1222](https://github.com/NousResearch/hermes-agent/pull/1222))
- Fix stale CI assumptions in parser and quick-command coverage ([#1236](https://github.com/NousResearch/hermes-agent/pull/1236))
- Fix gateway async tests without implicit event loop ([#1278](https://github.com/NousResearch/hermes-agent/pull/1278))
- Make gateway async tests xdist-safe ([#1281](https://github.com/NousResearch/hermes-agent/pull/1281))
- Cross-timezone naive timestamp regression for cron ([#1319](https://github.com/NousResearch/hermes-agent/pull/1319))
- Isolate codex provider tests from local env ([#1335](https://github.com/NousResearch/hermes-agent/pull/1335))
- Lock retry replacement semantics ([#1379](https://github.com/NousResearch/hermes-agent/pull/1379))
- Improve error logging in session search tool — by @aydnOktay ([#1533](https://github.com/NousResearch/hermes-agent/pull/1533))

---

## 📚 Documentation

- Comprehensive SOUL.md guide ([#1315](https://github.com/NousResearch/hermes-agent/pull/1315))
- Voice mode documentation ([#1316](https://github.com/NousResearch/hermes-agent/pull/1316), [#1362](https://github.com/NousResearch/hermes-agent/pull/1362))
- Provider contribution guide ([#1361](https://github.com/NousResearch/hermes-agent/pull/1361))
- ACP and internal systems implementation guides ([#1259](https://github.com/NousResearch/hermes-agent/pull/1259))
- Expand Docusaurus coverage across CLI, tools, skills, and skins ([#1232](https://github.com/NousResearch/hermes-agent/pull/1232))
- Terminal backend and Windows troubleshooting ([#1297](https://github.com/NousResearch/hermes-agent/pull/1297))
- Skills hub reference section ([#1317](https://github.com/NousResearch/hermes-agent/pull/1317))
- Checkpoint, /rollback, and git worktrees guide ([#1493](https://github.com/NousResearch/hermes-agent/pull/1493), [#1524](https://github.com/NousResearch/hermes-agent/pull/1524))
- CLI status bar and /usage reference ([#1523](https://github.com/NousResearch/hermes-agent/pull/1523))
- Fallback providers + /background command docs ([#1430](https://github.com/NousResearch/hermes-agent/pull/1430))
- Gateway service scopes docs ([#1378](https://github.com/NousResearch/hermes-agent/pull/1378))
- Slack thread reply behavior docs ([#1407](https://github.com/NousResearch/hermes-agent/pull/1407))
- Redesigned landing page with Nous blue palette — by @austinpickett ([#974](https://github.com/NousResearch/hermes-agent/pull/974))
- Fix several documentation typos — by @JackTheGit ([#953](https://github.com/NousResearch/hermes-agent/pull/953))
- Stabilize website diagrams ([#1405](https://github.com/NousResearch/hermes-agent/pull/1405))
- CLI vs messaging quick reference in README ([#1491](https://github.com/NousResearch/hermes-agent/pull/1491))
- Add search to Docusaurus ([#1053](https://github.com/NousResearch/hermes-agent/pull/1053))
- Home Assistant integration docs ([#1170](https://github.com/NousResearch/hermes-agent/pull/1170))

---

## 👥 Contributors

### Core
- **@teknium1** — 220+ PRs spanning every area of the codebase

### Top Community Contributors

- **@0xbyt4** (4 PRs) — Anthropic adapter fixes (max_tokens, fallback crash, 429/529 retry), Slack file upload thread context, setup NameError fix
- **@erosika** (1 PR) — Honcho memory integration: async writes, memory modes, session title integration
- **@SHL0MS** (2 PRs) — ASCII video skill design patterns and refactoring
- **@alt-glitch** (2 PRs) — Persistent shell mode for local/SSH backends, setuptools packaging fix
- **@arceus77-7** (2 PRs) — 1Password skill, fix skills list mislabeling
- **@kshitijk4poor** (1 PR) — OpenClaw migration during setup wizard
- **@ASRagab** (1 PR) — Fix adaptive thinking for Claude 4.6 models
- **@eren-karakus0** (1 PR) — Strip Hermes provider env vars from subprocess environment
- **@mr-emmett-one** (1 PR) — Fix DeepSeek V3 parser multi-tool call support
- **@jplew** (1 PR) — Gateway restart on retryable startup failures
- **@brandtcormorant** (1 PR) — Fix Anthropic cache control for empty text blocks
- **@aydnOktay** (1 PR) — Improve error logging in session search tool
- **@austinpickett** (1 PR) — Landing page redesign with Nous blue palette
- **@JackTheGit** (1 PR) — Documentation typo fixes

### All Contributors

@0xbyt4, @alt-glitch, @arceus77-7, @ASRagab, @austinpickett, @aydnOktay, @brandtcormorant, @eren-karakus0, @erosika, @JackTheGit, @jplew, @kshitijk4poor, @mr-emmett-one, @SHL0MS, @teknium1

---

**Full Changelog**: [v2026.3.12...v2026.3.17](https://github.com/NousResearch/hermes-agent/compare/v2026.3.12...v2026.3.17)


================================================
FILE: acp_adapter/__init__.py
================================================
"""ACP (Agent Communication Protocol) adapter for hermes-agent."""


================================================
FILE: acp_adapter/__main__.py
================================================
"""Allow running the ACP adapter as ``python -m acp_adapter``."""

from .entry import main

main()


================================================
FILE: acp_adapter/auth.py
================================================
"""ACP auth helpers — detect the currently configured Hermes provider."""

from __future__ import annotations

from typing import Optional


def detect_provider() -> Optional[str]:
    """Resolve the active Hermes runtime provider, or None if unavailable."""
    try:
        from hermes_cli.runtime_provider import resolve_runtime_provider
        runtime = resolve_runtime_provider()
        api_key = runtime.get("api_key")
        provider = runtime.get("provider")
        if isinstance(api_key, str) and api_key.strip() and isinstance(provider, str) and provider.strip():
            return provider.strip().lower()
    except Exception:
        return None
    return None


def has_provider() -> bool:
    """Return True if Hermes can resolve any runtime provider credentials."""
    return detect_provider() is not None


================================================
FILE: acp_adapter/entry.py
================================================
"""CLI entry point for the hermes-agent ACP adapter.

Loads environment variables from ``~/.hermes/.env``, configures logging
to write to stderr (so stdout is reserved for ACP JSON-RPC transport),
and starts the ACP agent server.

Usage::

    python -m acp_adapter.entry
    # or
    hermes acp
    # or
    hermes-acp
"""

import asyncio
import logging
import os
import sys
from pathlib import Path


def _setup_logging() -> None:
    """Route all logging to stderr so stdout stays clean for ACP stdio."""
    handler = logging.StreamHandler(sys.stderr)
    handler.setFormatter(
        logging.Formatter(
            "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
    )
    root = logging.getLogger()
    root.handlers.clear()
    root.addHandler(handler)
    root.setLevel(logging.INFO)

    # Quiet down noisy libraries
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpcore").setLevel(logging.WARNING)
    logging.getLogger("openai").setLevel(logging.WARNING)


def _load_env() -> None:
    """Load .env from HERMES_HOME (default ``~/.hermes``)."""
    from hermes_cli.env_loader import load_hermes_dotenv

    hermes_home = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
    loaded = load_hermes_dotenv(hermes_home=hermes_home)
    if loaded:
        for env_file in loaded:
            logging.getLogger(__name__).info("Loaded env from %s", env_file)
    else:
        logging.getLogger(__name__).info(
            "No .env found at %s, using system env", hermes_home / ".env"
        )


def main() -> None:
    """Entry point: load env, configure logging, run the ACP agent."""
    _setup_logging()
    _load_env()

    logger = logging.getLogger(__name__)
    logger.info("Starting hermes-agent ACP adapter")

    # Ensure the project root is on sys.path so ``from run_agent import AIAgent`` works
    project_root = str(Path(__file__).resolve().parent.parent)
    if project_root not in sys.path:
        sys.path.insert(0, project_root)

    import acp
    from .server import HermesACPAgent

    agent = HermesACPAgent()
    try:
        asyncio.run(acp.run_agent(agent))
    except KeyboardInterrupt:
        logger.info("Shutting down (KeyboardInterrupt)")
    except Exception:
        logger.exception("ACP agent crashed")
        sys.exit(1)


if __name__ == "__main__":
    main()


================================================
FILE: acp_adapter/events.py
================================================
"""Callback factories for bridging AIAgent events to ACP notifications.

Each factory returns a callable with the signature that AIAgent expects
for its callbacks. Internally, the callbacks push ACP session updates
to the client via ``conn.session_update()`` using
``asyncio.run_coroutine_threadsafe()`` (since AIAgent runs in a worker
thread while the event loop lives on the main thread).
"""

import asyncio
import json
import logging
from collections import defaultdict, deque
from typing import Any, Callable, Deque, Dict

import acp

from .tools import (
    build_tool_complete,
    build_tool_start,
    make_tool_call_id,
)

logger = logging.getLogger(__name__)


def _send_update(
    conn: acp.Client,
    session_id: str,
    loop: asyncio.AbstractEventLoop,
    update: Any,
) -> None:
    """Fire-and-forget an ACP session update from a worker thread."""
    try:
        future = asyncio.run_coroutine_threadsafe(
            conn.session_update(session_id, update), loop
        )
        future.result(timeout=5)
    except Exception:
        logger.debug("Failed to send ACP update", exc_info=True)


# ------------------------------------------------------------------
# Tool progress callback
# ------------------------------------------------------------------

def make_tool_progress_cb(
    conn: acp.Client,
    session_id: str,
    loop: asyncio.AbstractEventLoop,
    tool_call_ids: Dict[str, Deque[str]],
) -> Callable:
    """Create a ``tool_progress_callback`` for AIAgent.

    Signature expected by AIAgent::

        tool_progress_callback(name: str, preview: str, args: dict)

    Emits ``ToolCallStart`` for each tool invocation and tracks IDs in a FIFO
    queue per tool name so duplicate/parallel same-name calls still complete
    against the correct ACP tool call.
    """

    def _tool_progress(name: str, preview: str, args: Any = None) -> None:
        if isinstance(args, str):
            try:
                args = json.loads(args)
            except (json.JSONDecodeError, TypeError):
                args = {"raw": args}
        if not isinstance(args, dict):
            args = {}

        tc_id = make_tool_call_id()
        queue = tool_call_ids.get(name)
        if queue is None:
            queue = deque()
            tool_call_ids[name] = queue
        elif isinstance(queue, str):
            queue = deque([queue])
            tool_call_ids[name] = queue
        queue.append(tc_id)

        update = build_tool_start(tc_id, name, args)
        _send_update(conn, session_id, loop, update)

    return _tool_progress


# ------------------------------------------------------------------
# Thinking callback
# ------------------------------------------------------------------

def make_thinking_cb(
    conn: acp.Client,
    session_id: str,
    loop: asyncio.AbstractEventLoop,
) -> Callable:
    """Create a ``thinking_callback`` for AIAgent."""

    def _thinking(text: str) -> None:
        if not text:
            return
        update = acp.update_agent_thought_text(text)
        _send_update(conn, session_id, loop, update)

    return _thinking


# ------------------------------------------------------------------
# Step callback
# ------------------------------------------------------------------

def make_step_cb(
    conn: acp.Client,
    session_id: str,
    loop: asyncio.AbstractEventLoop,
    tool_call_ids: Dict[str, Deque[str]],
) -> Callable:
    """Create a ``step_callback`` for AIAgent.

    Signature expected by AIAgent::

        step_callback(api_call_count: int, prev_tools: list)
    """

    def _step(api_call_count: int, prev_tools: Any = None) -> None:
        if prev_tools and isinstance(prev_tools, list):
            for tool_info in prev_tools:
                tool_name = None
                result = None

                if isinstance(tool_info, dict):
                    tool_name = tool_info.get("name") or tool_info.get("function_name")
                    result = tool_info.get("result") or tool_info.get("output")
                elif isinstance(tool_info, str):
                    tool_name = tool_info

                queue = tool_call_ids.get(tool_name or "")
                if isinstance(queue, str):
                    queue = deque([queue])
                    tool_call_ids[tool_name] = queue
                if tool_name and queue:
                    tc_id = queue.popleft()
                    update = build_tool_complete(
                        tc_id, tool_name, result=str(result) if result is not None else None
                    )
                    _send_update(conn, session_id, loop, update)
                    if not queue:
                        tool_call_ids.pop(tool_name, None)

    return _step


# ------------------------------------------------------------------
# Agent message callback
# ------------------------------------------------------------------

def make_message_cb(
    conn: acp.Client,
    session_id: str,
    loop: asyncio.AbstractEventLoop,
) -> Callable:
    """Create a callback that streams agent response text to the editor."""

    def _message(text: str) -> None:
        if not text:
            return
        update = acp.update_agent_message_text(text)
        _send_update(conn, session_id, loop, update)

    return _message


================================================
FILE: acp_adapter/permissions.py
================================================
"""ACP permission bridging — maps ACP approval requests to hermes approval callbacks."""

from __future__ import annotations

import asyncio
import logging
from concurrent.futures import TimeoutError as FutureTimeout
from typing import Any, Callable, Optional

from acp.schema import (
    AllowedOutcome,
    DeniedOutcome,
    PermissionOption,
    RequestPermissionRequest,
    SelectedPermissionOutcome,
)

logger = logging.getLogger(__name__)

# Maps ACP PermissionOptionKind -> hermes approval result strings
_KIND_TO_HERMES = {
    "allow_once": "once",
    "allow_always": "always",
    "reject_once": "deny",
    "reject_always": "deny",
}


def make_approval_callback(
    request_permission_fn: Callable,
    loop: asyncio.AbstractEventLoop,
    session_id: str,
    timeout: float = 60.0,
) -> Callable[[str, str], str]:
    """
    Return a hermes-compatible ``approval_callback(command, description) -> str``
    that bridges to the ACP client's ``request_permission`` call.

    Args:
        request_permission_fn: The ACP connection's ``request_permission`` coroutine.
        loop: The event loop on which the ACP connection lives.
        session_id: Current ACP session id.
        timeout: Seconds to wait for a response before auto-denying.
    """

    def _callback(command: str, description: str) -> str:
        options = [
            PermissionOption(option_id="allow_once", kind="allow_once", name="Allow once"),
            PermissionOption(option_id="allow_always", kind="allow_always", name="Allow always"),
            PermissionOption(option_id="deny", kind="reject_once", name="Deny"),
        ]
        import acp as _acp

        tool_call = _acp.start_tool_call("perm-check", command, kind="execute")

        coro = request_permission_fn(
            session_id=session_id,
            tool_call=tool_call,
            options=options,
        )

        try:
            future = asyncio.run_coroutine_threadsafe(coro, loop)
            response = future.result(timeout=timeout)
        except (FutureTimeout, Exception) as exc:
            logger.warning("Permission request timed out or failed: %s", exc)
            return "deny"

        outcome = response.outcome
        if isinstance(outcome, AllowedOutcome):
            option_id = outcome.option_id
            # Look up the kind from our options list
            for opt in options:
                if opt.option_id == option_id:
                    return _KIND_TO_HERMES.get(opt.kind, "deny")
            return "once"  # fallback for unknown option_id
        else:
            return "deny"

    return _callback


================================================
FILE: acp_adapter/server.py
================================================
"""ACP agent server — exposes Hermes Agent via the Agent Client Protocol."""

from __future__ import annotations

import asyncio
import logging
from collections import defaultdict, deque
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Deque, Optional

import acp
from acp.schema import (
    AgentCapabilities,
    AuthenticateResponse,
    AuthMethod,
    ClientCapabilities,
    EmbeddedResourceContentBlock,
    ForkSessionResponse,
    ImageContentBlock,
    AudioContentBlock,
    Implementation,
    InitializeResponse,
    ListSessionsResponse,
    LoadSessionResponse,
    NewSessionResponse,
    PromptResponse,
    ResumeSessionResponse,
    ResourceContentBlock,
    SessionCapabilities,
    SessionForkCapabilities,
    SessionListCapabilities,
    SessionInfo,
    TextContentBlock,
    Usage,
)

from acp_adapter.auth import detect_provider, has_provider
from acp_adapter.events import (
    make_message_cb,
    make_step_cb,
    make_thinking_cb,
    make_tool_progress_cb,
)
from acp_adapter.permissions import make_approval_callback
from acp_adapter.session import SessionManager, SessionState

logger = logging.getLogger(__name__)

try:
    from hermes_cli import __version__ as HERMES_VERSION
except Exception:
    HERMES_VERSION = "0.0.0"

# Thread pool for running AIAgent (synchronous) in parallel.
_executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="acp-agent")


def _extract_text(
    prompt: list[
        TextContentBlock
        | ImageContentBlock
        | AudioContentBlock
        | ResourceContentBlock
        | EmbeddedResourceContentBlock
    ],
) -> str:
    """Extract plain text from ACP content blocks."""
    parts: list[str] = []
    for block in prompt:
        if isinstance(block, TextContentBlock):
            parts.append(block.text)
        elif hasattr(block, "text"):
            parts.append(str(block.text))
        # Non-text blocks are ignored for now.
    return "\n".join(parts)


class HermesACPAgent(acp.Agent):
    """ACP Agent implementation wrapping Hermes AIAgent."""

    def __init__(self, session_manager: SessionManager | None = None):
        super().__init__()
        self.session_manager = session_manager or SessionManager()
        self._conn: Optional[acp.Client] = None

    # ---- Connection lifecycle -----------------------------------------------

    def on_connect(self, conn: acp.Client) -> None:
        """Store the client connection for sending session updates."""
        self._conn = conn
        logger.info("ACP client connected")

    # ---- ACP lifecycle ------------------------------------------------------

    async def initialize(
        self,
        protocol_version: int,
        client_capabilities: ClientCapabilities | None = None,
        client_info: Implementation | None = None,
        **kwargs: Any,
    ) -> InitializeResponse:
        provider = detect_provider()
        auth_methods = None
        if provider:
            auth_methods = [
                AuthMethod(
                    id=provider,
                    name=f"{provider} runtime credentials",
                    description=f"Authenticate Hermes using the currently configured {provider} runtime credentials.",
                )
            ]

        client_name = client_info.name if client_info else "unknown"
        logger.info("Initialize from %s (protocol v%s)", client_name, protocol_version)

        return InitializeResponse(
            protocol_version=acp.PROTOCOL_VERSION,
            agent_info=Implementation(name="hermes-agent", version=HERMES_VERSION),
            agent_capabilities=AgentCapabilities(
                session_capabilities=SessionCapabilities(
                    fork=SessionForkCapabilities(),
                    list=SessionListCapabilities(),
                ),
            ),
            auth_methods=auth_methods,
        )

    async def authenticate(self, method_id: str, **kwargs: Any) -> AuthenticateResponse | None:
        if has_provider():
            return AuthenticateResponse()
        return None

    # ---- Session management -------------------------------------------------

    async def new_session(
        self,
        cwd: str,
        mcp_servers: list | None = None,
        **kwargs: Any,
    ) -> NewSessionResponse:
        state = self.session_manager.create_session(cwd=cwd)
        logger.info("New session %s (cwd=%s)", state.session_id, cwd)
        return NewSessionResponse(session_id=state.session_id)

    async def load_session(
        self,
        cwd: str,
        session_id: str,
        mcp_servers: list | None = None,
        **kwargs: Any,
    ) -> LoadSessionResponse | None:
        state = self.session_manager.update_cwd(session_id, cwd)
        if state is None:
            logger.warning("load_session: session %s not found", session_id)
            return None
        logger.info("Loaded session %s", session_id)
        return LoadSessionResponse()

    async def resume_session(
        self,
        cwd: str,
        session_id: str,
        mcp_servers: list | None = None,
        **kwargs: Any,
    ) -> ResumeSessionResponse:
        state = self.session_manager.update_cwd(session_id, cwd)
        if state is None:
            logger.warning("resume_session: session %s not found, creating new", session_id)
            state = self.session_manager.create_session(cwd=cwd)
        logger.info("Resumed session %s", state.session_id)
        return ResumeSessionResponse()

    async def cancel(self, session_id: str, **kwargs: Any) -> None:
        state = self.session_manager.get_session(session_id)
        if state and state.cancel_event:
            state.cancel_event.set()
            try:
                if getattr(state, "agent", None) and hasattr(state.agent, "interrupt"):
                    state.agent.interrupt()
            except Exception:
                logger.debug("Failed to interrupt ACP session %s", session_id, exc_info=True)
            logger.info("Cancelled session %s", session_id)

    async def fork_session(
        self,
        cwd: str,
        session_id: str,
        mcp_servers: list | None = None,
        **kwargs: Any,
    ) -> ForkSessionResponse:
        state = self.session_manager.fork_session(session_id, cwd=cwd)
        new_id = state.session_id if state else ""
        logger.info("Forked session %s -> %s", session_id, new_id)
        return ForkSessionResponse(session_id=new_id)

    async def list_sessions(
        self,
        cursor: str | None = None,
        cwd: str | None = None,
        **kwargs: Any,
    ) -> ListSessionsResponse:
        infos = self.session_manager.list_sessions()
        sessions = [
            SessionInfo(session_id=s["session_id"], cwd=s["cwd"])
            for s in infos
        ]
        return ListSessionsResponse(sessions=sessions)

    # ---- Prompt (core) ------------------------------------------------------

    async def prompt(
        self,
        prompt: list[
            TextContentBlock
            | ImageContentBlock
            | AudioContentBlock
            | ResourceContentBlock
            | EmbeddedResourceContentBlock
        ],
        session_id: str,
        **kwargs: Any,
    ) -> PromptResponse:
        """Run Hermes on the user's prompt and stream events back to the editor."""
        state = self.session_manager.get_session(session_id)
        if state is None:
            logger.error("prompt: session %s not found", session_id)
            return PromptResponse(stop_reason="refusal")

        user_text = _extract_text(prompt).strip()
        if not user_text:
            return PromptResponse(stop_reason="end_turn")

        # Intercept slash commands — handle locally without calling the LLM
        if user_text.startswith("/"):
            response_text = self._handle_slash_command(user_text, state)
            if response_text is not None:
                if self._conn:
                    update = acp.update_agent_message_text(response_text)
                    await self._conn.session_update(session_id, update)
                return PromptResponse(stop_reason="end_turn")

        logger.info("Prompt on session %s: %s", session_id, user_text[:100])

        conn = self._conn
        loop = asyncio.get_running_loop()

        if state.cancel_event:
            state.cancel_event.clear()

        tool_call_ids: dict[str, Deque[str]] = defaultdict(deque)
        previous_approval_cb = None

        if conn:
            tool_progress_cb = make_tool_progress_cb(conn, session_id, loop, tool_call_ids)
            thinking_cb = make_thinking_cb(conn, session_id, loop)
            step_cb = make_step_cb(conn, session_id, loop, tool_call_ids)
            message_cb = make_message_cb(conn, session_id, loop)
            approval_cb = make_approval_callback(conn.request_permission, loop, session_id)
        else:
            tool_progress_cb = None
            thinking_cb = None
            step_cb = None
            message_cb = None
            approval_cb = None

        agent = state.agent
        agent.tool_progress_callback = tool_progress_cb
        agent.thinking_callback = thinking_cb
        agent.step_callback = step_cb
        agent.message_callback = message_cb

        if approval_cb:
            try:
                from tools import terminal_tool as _terminal_tool
                previous_approval_cb = getattr(_terminal_tool, "_approval_callback", None)
                _terminal_tool.set_approval_callback(approval_cb)
            except Exception:
                logger.debug("Could not set ACP approval callback", exc_info=True)

        def _run_agent() -> dict:
            try:
                result = agent.run_conversation(
                    user_message=user_text,
                    conversation_history=state.history,
                    task_id=session_id,
                )
                return result
            except Exception as e:
                logger.exception("Agent error in session %s", session_id)
                return {"final_response": f"Error: {e}", "messages": state.history}
            finally:
                if approval_cb:
                    try:
                        from tools import terminal_tool as _terminal_tool
                        _terminal_tool.set_approval_callback(previous_approval_cb)
                    except Exception:
                        logger.debug("Could not restore approval callback", exc_info=True)

        try:
            result = await loop.run_in_executor(_executor, _run_agent)
        except Exception:
            logger.exception("Executor error for session %s", session_id)
            return PromptResponse(stop_reason="end_turn")

        if result.get("messages"):
            state.history = result["messages"]
            # Persist updated history so sessions survive process restarts.
            self.session_manager.save_session(session_id)

        final_response = result.get("final_response", "")
        if final_response and conn:
            update = acp.update_agent_message_text(final_response)
            await conn.session_update(session_id, update)

        usage = None
        usage_data = result.get("usage")
        if usage_data and isinstance(usage_data, dict):
            usage = Usage(
                input_tokens=usage_data.get("prompt_tokens", 0),
                output_tokens=usage_data.get("completion_tokens", 0),
                total_tokens=usage_data.get("total_tokens", 0),
                thought_tokens=usage_data.get("reasoning_tokens"),
                cached_read_tokens=usage_data.get("cached_tokens"),
            )

        stop_reason = "cancelled" if state.cancel_event and state.cancel_event.is_set() else "end_turn"
        return PromptResponse(stop_reason=stop_reason, usage=usage)

    # ---- Slash commands (headless) -------------------------------------------

    _SLASH_COMMANDS = {
        "help": "Show available commands",
        "model": "Show or change current model",
        "tools": "List available tools",
        "context": "Show conversation context info",
        "reset": "Clear conversation history",
        "compact": "Compress conversation context",
        "version": "Show Hermes version",
    }

Download .txt
gitextract_1adcsei_/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── setup_help.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── deploy-site.yml
│       ├── docs-site-checks.yml
│       └── tests.yml
├── .gitignore
├── .gitmodules
├── .plans/
│   ├── openai-api-server.md
│   └── streaming-support.md
├── AGENTS.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASE_v0.2.0.md
├── RELEASE_v0.3.0.md
├── acp_adapter/
│   ├── __init__.py
│   ├── __main__.py
│   ├── auth.py
│   ├── entry.py
│   ├── events.py
│   ├── permissions.py
│   ├── server.py
│   ├── session.py
│   └── tools.py
├── acp_registry/
│   └── agent.json
├── agent/
│   ├── __init__.py
│   ├── anthropic_adapter.py
│   ├── auxiliary_client.py
│   ├── context_compressor.py
│   ├── copilot_acp_client.py
│   ├── display.py
│   ├── insights.py
│   ├── model_metadata.py
│   ├── models_dev.py
│   ├── prompt_builder.py
│   ├── prompt_caching.py
│   ├── redact.py
│   ├── skill_commands.py
│   ├── smart_model_routing.py
│   ├── title_generator.py
│   ├── trajectory.py
│   └── usage_pricing.py
├── batch_runner.py
├── cli-config.yaml.example
├── cli.py
├── cron/
│   ├── __init__.py
│   ├── jobs.py
│   └── scheduler.py
├── datagen-config-examples/
│   ├── example_browser_tasks.jsonl
│   ├── run_browser_tasks.sh
│   ├── trajectory_compression.yaml
│   └── web_research.yaml
├── docs/
│   ├── acp-setup.md
│   ├── honcho-integration-spec.html
│   ├── honcho-integration-spec.md
│   ├── migration/
│   │   └── openclaw.md
│   ├── plans/
│   │   └── 2026-03-16-pricing-accuracy-architecture-design.md
│   └── skins/
│       └── example-skin.yaml
├── environments/
│   ├── README.md
│   ├── __init__.py
│   ├── agent_loop.py
│   ├── agentic_opd_env.py
│   ├── benchmarks/
│   │   ├── __init__.py
│   │   ├── tblite/
│   │   │   ├── README.md
│   │   │   ├── __init__.py
│   │   │   ├── default.yaml
│   │   │   ├── local.yaml
│   │   │   ├── local_vllm.yaml
│   │   │   ├── run_eval.sh
│   │   │   └── tblite_env.py
│   │   ├── terminalbench_2/
│   │   │   ├── __init__.py
│   │   │   ├── default.yaml
│   │   │   ├── run_eval.sh
│   │   │   └── terminalbench2_env.py
│   │   └── yc_bench/
│   │       ├── README.md
│   │       ├── __init__.py
│   │       ├── default.yaml
│   │       ├── run_eval.sh
│   │       └── yc_bench_env.py
│   ├── hermes_base_env.py
│   ├── hermes_swe_env/
│   │   ├── __init__.py
│   │   ├── default.yaml
│   │   └── hermes_swe_env.py
│   ├── patches.py
│   ├── terminal_test_env/
│   │   ├── __init__.py
│   │   ├── default.yaml
│   │   └── terminal_test_env.py
│   ├── tool_call_parsers/
│   │   ├── __init__.py
│   │   ├── deepseek_v3_1_parser.py
│   │   ├── deepseek_v3_parser.py
│   │   ├── glm45_parser.py
│   │   ├── glm47_parser.py
│   │   ├── hermes_parser.py
│   │   ├── kimi_k2_parser.py
│   │   ├── llama_parser.py
│   │   ├── longcat_parser.py
│   │   ├── mistral_parser.py
│   │   ├── qwen3_coder_parser.py
│   │   └── qwen_parser.py
│   ├── tool_context.py
│   └── web_research_env.py
├── gateway/
│   ├── __init__.py
│   ├── channel_directory.py
│   ├── config.py
│   ├── delivery.py
│   ├── hooks.py
│   ├── mirror.py
│   ├── pairing.py
│   ├── platforms/
│   │   ├── ADDING_A_PLATFORM.md
│   │   ├── __init__.py
│   │   ├── api_server.py
│   │   ├── base.py
│   │   ├── dingtalk.py
│   │   ├── discord.py
│   │   ├── email.py
│   │   ├── homeassistant.py
│   │   ├── matrix.py
│   │   ├── mattermost.py
│   │   ├── signal.py
│   │   ├── slack.py
│   │   ├── sms.py
│   │   ├── telegram.py
│   │   ├── webhook.py
│   │   └── whatsapp.py
│   ├── run.py
│   ├── session.py
│   ├── status.py
│   ├── sticker_cache.py
│   └── stream_consumer.py
├── hermes
├── hermes_cli/
│   ├── __init__.py
│   ├── auth.py
│   ├── banner.py
│   ├── callbacks.py
│   ├── checklist.py
│   ├── claw.py
│   ├── clipboard.py
│   ├── codex_models.py
│   ├── colors.py
│   ├── commands.py
│   ├── config.py
│   ├── copilot_auth.py
│   ├── cron.py
│   ├── curses_ui.py
│   ├── default_soul.py
│   ├── doctor.py
│   ├── env_loader.py
│   ├── gateway.py
│   ├── main.py
│   ├── models.py
│   ├── pairing.py
│   ├── plugins.py
│   ├── runtime_provider.py
│   ├── setup.py
│   ├── skills_config.py
│   ├── skills_hub.py
│   ├── skin_engine.py
│   ├── status.py
│   ├── tools_config.py
│   └── uninstall.py
├── hermes_constants.py
├── hermes_state.py
├── hermes_time.py
├── honcho_integration/
│   ├── __init__.py
│   ├── cli.py
│   ├── client.py
│   └── session.py
├── landingpage/
│   ├── index.html
│   ├── script.js
│   └── style.css
├── mini_swe_runner.py
├── minisweagent_path.py
├── model_tools.py
├── optional-skills/
│   ├── DESCRIPTION.md
│   ├── autonomous-ai-agents/
│   │   ├── DESCRIPTION.md
│   │   └── blackbox/
│   │       └── SKILL.md
│   ├── blockchain/
│   │   ├── base/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── base_client.py
│   │   └── solana/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── solana_client.py
│   ├── creative/
│   │   └── blender-mcp/
│   │       └── SKILL.md
│   ├── email/
│   │   └── agentmail/
│   │       └── SKILL.md
│   ├── health/
│   │   ├── DESCRIPTION.md
│   │   └── neuroskill-bci/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── api.md
│   │           ├── metrics.md
│   │           └── protocols.md
│   ├── mcp/
│   │   ├── DESCRIPTION.md
│   │   └── fastmcp/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── fastmcp-cli.md
│   │       ├── scripts/
│   │       │   └── scaffold_fastmcp.py
│   │       └── templates/
│   │           ├── api_wrapper.py
│   │           ├── database_server.py
│   │           └── file_processor.py
│   ├── migration/
│   │   ├── DESCRIPTION.md
│   │   └── openclaw-migration/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── openclaw_to_hermes.py
│   ├── productivity/
│   │   └── telephony/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── telephony.py
│   ├── research/
│   │   └── qmd/
│   │       └── SKILL.md
│   └── security/
│       ├── 1password/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── cli-examples.md
│       │       └── get-started.md
│       ├── DESCRIPTION.md
│       ├── oss-forensics/
│       │   ├── SKILL.md
│       │   ├── references/
│       │   │   ├── evidence-types.md
│       │   │   ├── github-archive-guide.md
│       │   │   ├── investigation-templates.md
│       │   │   └── recovery-techniques.md
│       │   ├── scripts/
│       │   │   └── evidence-store.py
│       │   └── templates/
│       │       ├── forensic-report.md
│       │       └── malicious-package-report.md
│       └── sherlock/
│           └── SKILL.md
├── package.json
├── pyproject.toml
├── requirements.txt
├── rl_cli.py
├── run_agent.py
├── scripts/
│   ├── discord-voice-doctor.py
│   ├── hermes-gateway
│   ├── install.cmd
│   ├── install.ps1
│   ├── install.sh
│   ├── kill_modal.sh
│   ├── release.py
│   ├── sample_and_compress.py
│   └── whatsapp-bridge/
│       ├── bridge.js
│       └── package.json
├── setup-hermes.sh
├── skills/
│   ├── apple/
│   │   ├── DESCRIPTION.md
│   │   ├── apple-notes/
│   │   │   └── SKILL.md
│   │   ├── apple-reminders/
│   │   │   └── SKILL.md
│   │   ├── findmy/
│   │   │   └── SKILL.md
│   │   └── imessage/
│   │       └── SKILL.md
│   ├── autonomous-ai-agents/
│   │   ├── DESCRIPTION.md
│   │   ├── claude-code/
│   │   │   └── SKILL.md
│   │   ├── codex/
│   │   │   └── SKILL.md
│   │   ├── hermes-agent/
│   │   │   └── SKILL.md
│   │   └── opencode/
│   │       └── SKILL.md
│   ├── creative/
│   │   ├── DESCRIPTION.md
│   │   ├── ascii-art/
│   │   │   └── SKILL.md
│   │   ├── ascii-video/
│   │   │   ├── README.md
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       ├── architecture.md
│   │   │       ├── composition.md
│   │   │       ├── effects.md
│   │   │       ├── inputs.md
│   │   │       ├── optimization.md
│   │   │       ├── scenes.md
│   │   │       ├── shaders.md
│   │   │       └── troubleshooting.md
│   │   └── excalidraw/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   ├── colors.md
│   │       │   ├── dark-mode.md
│   │       │   └── examples.md
│   │       └── scripts/
│   │           └── upload.py
│   ├── data-science/
│   │   ├── DESCRIPTION.md
│   │   └── jupyter-live-kernel/
│   │       └── SKILL.md
│   ├── diagramming/
│   │   └── DESCRIPTION.md
│   ├── dogfood/
│   │   ├── SKILL.md
│   │   ├── hermes-agent-setup/
│   │   │   └── SKILL.md
│   │   ├── references/
│   │   │   └── issue-taxonomy.md
│   │   └── templates/
│   │       └── dogfood-report-template.md
│   ├── domain/
│   │   └── DESCRIPTION.md
│   ├── email/
│   │   ├── DESCRIPTION.md
│   │   └── himalaya/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── configuration.md
│   │           └── message-composition.md
│   ├── feeds/
│   │   └── DESCRIPTION.md
│   ├── gaming/
│   │   ├── DESCRIPTION.md
│   │   ├── minecraft-modpack-server/
│   │   │   └── SKILL.md
│   │   └── pokemon-player/
│   │       └── SKILL.md
│   ├── gifs/
│   │   └── DESCRIPTION.md
│   ├── github/
│   │   ├── DESCRIPTION.md
│   │   ├── codebase-inspection/
│   │   │   └── SKILL.md
│   │   ├── github-auth/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── gh-env.sh
│   │   ├── github-code-review/
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       └── review-output-template.md
│   │   ├── github-issues/
│   │   │   ├── SKILL.md
│   │   │   └── templates/
│   │   │       ├── bug-report.md
│   │   │       └── feature-request.md
│   │   ├── github-pr-workflow/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   ├── ci-troubleshooting.md
│   │   │   │   └── conventional-commits.md
│   │   │   └── templates/
│   │   │       ├── pr-body-bugfix.md
│   │   │       └── pr-body-feature.md
│   │   └── github-repo-management/
│   │       ├── SKILL.md
│   │       └── references/
│   │           └── github-api-cheatsheet.md
│   ├── index-cache/
│   │   ├── anthropics_skills_skills_.json
│   │   ├── claude_marketplace_anthropics_skills.json
│   │   ├── lobehub_index.json
│   │   └── openai_skills_skills_.json
│   ├── inference-sh/
│   │   ├── DESCRIPTION.md
│   │   └── cli/
│   │       ├── SKILL.md
│   │       └── references/
│   │           ├── app-discovery.md
│   │           ├── authentication.md
│   │           ├── cli-reference.md
│   │           └── running-apps.md
│   ├── leisure/
│   │   └── find-nearby/
│   │       ├── SKILL.md
│   │       └── scripts/
│   │           └── find_nearby.py
│   ├── mcp/
│   │   ├── DESCRIPTION.md
│   │   ├── mcporter/
│   │   │   └── SKILL.md
│   │   └── native-mcp/
│   │       └── SKILL.md
│   ├── media/
│   │   ├── DESCRIPTION.md
│   │   ├── gif-search/
│   │   │   └── SKILL.md
│   │   ├── heartmula/
│   │   │   └── SKILL.md
│   │   ├── songsee/
│   │   │   └── SKILL.md
│   │   └── youtube-content/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── output-formats.md
│   │       └── scripts/
│   │           └── fetch_transcript.py
│   ├── mlops/
│   │   ├── DESCRIPTION.md
│   │   ├── cloud/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── lambda-labs/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   └── modal/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── advanced-usage.md
│   │   │           └── troubleshooting.md
│   │   ├── evaluation/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── huggingface-tokenizers/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── algorithms.md
│   │   │   │       ├── integration.md
│   │   │   │       ├── pipeline.md
│   │   │   │       └── training.md
│   │   │   ├── lm-evaluation-harness/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api-evaluation.md
│   │   │   │       ├── benchmark-guide.md
│   │   │   │       ├── custom-tasks.md
│   │   │   │       └── distributed-eval.md
│   │   │   ├── nemo-curator/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── deduplication.md
│   │   │   │       └── filtering.md
│   │   │   ├── saelens/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── README.md
│   │   │   │       ├── api.md
│   │   │   │       └── tutorials.md
│   │   │   └── weights-and-biases/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── artifacts.md
│   │   │           ├── integrations.md
│   │   │           └── sweeps.md
│   │   ├── huggingface-hub/
│   │   │   └── SKILL.md
│   │   ├── inference/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── gguf/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── guidance/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── backends.md
│   │   │   │       ├── constraints.md
│   │   │   │       └── examples.md
│   │   │   ├── instructor/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── examples.md
│   │   │   │       ├── providers.md
│   │   │   │       └── validation.md
│   │   │   ├── llama-cpp/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── optimization.md
│   │   │   │       ├── quantization.md
│   │   │   │       └── server.md
│   │   │   ├── obliteratus/
│   │   │   │   ├── SKILL.md
│   │   │   │   ├── references/
│   │   │   │   │   ├── analysis-modules.md
│   │   │   │   │   └── methods-guide.md
│   │   │   │   └── templates/
│   │   │   │       ├── abliteration-config.yaml
│   │   │   │       ├── analysis-study.yaml
│   │   │   │       └── batch-abliteration.yaml
│   │   │   ├── outlines/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── backends.md
│   │   │   │       ├── examples.md
│   │   │   │       └── json_generation.md
│   │   │   ├── tensorrt-llm/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── multi-gpu.md
│   │   │   │       ├── optimization.md
│   │   │   │       └── serving.md
│   │   │   └── vllm/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── optimization.md
│   │   │           ├── quantization.md
│   │   │           ├── server-deployment.md
│   │   │           └── troubleshooting.md
│   │   ├── models/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── audiocraft/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── clip/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       └── applications.md
│   │   │   ├── llava/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       └── training.md
│   │   │   ├── segment-anything/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── stable-diffusion/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   └── whisper/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           └── languages.md
│   │   ├── research/
│   │   │   ├── DESCRIPTION.md
│   │   │   └── dspy/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── examples.md
│   │   │           ├── modules.md
│   │   │           └── optimizers.md
│   │   ├── training/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── accelerate/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── custom-plugins.md
│   │   │   │       ├── megatron-integration.md
│   │   │   │       └── performance.md
│   │   │   ├── axolotl/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api.md
│   │   │   │       ├── dataset-formats.md
│   │   │   │       ├── index.md
│   │   │   │       └── other.md
│   │   │   ├── flash-attention/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── benchmarks.md
│   │   │   │       └── transformers-integration.md
│   │   │   ├── grpo-rl-training/
│   │   │   │   ├── README.md
│   │   │   │   ├── SKILL.md
│   │   │   │   └── templates/
│   │   │   │       └── basic_grpo_training.py
│   │   │   ├── hermes-atropos-environments/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── agentresult-fields.md
│   │   │   │       ├── atropos-base-env.md
│   │   │   │       └── usage-patterns.md
│   │   │   ├── peft/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── advanced-usage.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── pytorch-fsdp/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── index.md
│   │   │   │       └── other.md
│   │   │   ├── pytorch-lightning/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── callbacks.md
│   │   │   │       ├── distributed.md
│   │   │   │       └── hyperparameter-tuning.md
│   │   │   ├── simpo/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── datasets.md
│   │   │   │       ├── hyperparameters.md
│   │   │   │       └── loss-functions.md
│   │   │   ├── slime/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── api-reference.md
│   │   │   │       └── troubleshooting.md
│   │   │   ├── torchtitan/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── checkpoint.md
│   │   │   │       ├── custom-models.md
│   │   │   │       ├── float8.md
│   │   │   │       └── fsdp.md
│   │   │   ├── trl-fine-tuning/
│   │   │   │   ├── SKILL.md
│   │   │   │   └── references/
│   │   │   │       ├── dpo-variants.md
│   │   │   │       ├── online-rl.md
│   │   │   │       ├── reward-modeling.md
│   │   │   │       └── sft-training.md
│   │   │   └── unsloth/
│   │   │       ├── SKILL.md
│   │   │       └── references/
│   │   │           ├── index.md
│   │   │           ├── llms-full.md
│   │   │           ├── llms-txt.md
│   │   │           └── llms.md
│   │   └── vector-databases/
│   │       ├── DESCRIPTION.md
│   │       ├── chroma/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── integration.md
│   │       ├── faiss/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── index_types.md
│   │       ├── pinecone/
│   │       │   ├── SKILL.md
│   │       │   └── references/
│   │       │       └── deployment.md
│   │       └── qdrant/
│   │           ├── SKILL.md
│   │           └── references/
│   │               ├── advanced-usage.md
│   │               └── troubleshooting.md
│   ├── music-creation/
│   │   └── DESCRIPTION.md
│   ├── note-taking/
│   │   ├── DESCRIPTION.md
│   │   └── obsidian/
│   │       └── SKILL.md
│   ├── productivity/
│   │   ├── DESCRIPTION.md
│   │   ├── google-workspace/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   └── gmail-search-syntax.md
│   │   │   └── scripts/
│   │   │       ├── google_api.py
│   │   │       └── setup.py
│   │   ├── linear/
│   │   │   └── SKILL.md
│   │   ├── nano-pdf/
│   │   │   └── SKILL.md
│   │   ├── notion/
│   │   │   ├── SKILL.md
│   │   │   └── references/
│   │   │       └── block-types.md
│   │   ├── ocr-and-documents/
│   │   │   ├── DESCRIPTION.md
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       ├── extract_marker.py
│   │   │       └── extract_pymupdf.py
│   │   └── powerpoint/
│   │       ├── LICENSE.txt
│   │       ├── SKILL.md
│   │       ├── editing.md
│   │       ├── pptxgenjs.md
│   │       └── scripts/
│   │           ├── __init__.py
│   │           ├── add_slide.py
│   │           ├── clean.py
│   │           └── office/
│   │               ├── helpers/
│   │               │   ├── __init__.py
│   │               │   ├── merge_runs.py
│   │               │   └── simplify_redlines.py
│   │               ├── pack.py
│   │               └── schemas/
│   │                   ├── ISO-IEC29500-4_2016/
│   │                   │   ├── dml-chart.xsd
│   │                   │   ├── dml-chartDrawing.xsd
│   │                   │   ├── dml-diagram.xsd
│   │                   │   ├── dml-lockedCanvas.xsd
│   │                   │   ├── dml-main.xsd
│   │                   │   ├── dml-picture.xsd
│   │                   │   ├── dml-spreadsheetDrawing.xsd
│   │                   │   ├── dml-wordprocessingDrawing.xsd
│   │                   │   ├── pml.xsd
│   │                   │   ├── shared-additionalCharacteristics.xsd
│   │                   │   ├── shared-bibliography.xsd
│   │                   │   ├── shared-commonSimpleTypes.xsd
│   │                   │   ├── shared-customXmlDataProperties.xsd
│   │                   │   ├── shared-customXmlSchemaProperties.xsd
│   │                   │   ├── shared-documentPropertiesCustom.xsd
│   │                   │   ├── shared-documentPropertiesExtended.xsd
│   │                   │   ├── shared-documentPropertiesVariantTypes.xsd
│   │                   │   ├── shared-math.xsd
│   │                   │   ├── shared-relationshipReference.xsd
│   │                   │   ├── sml.xsd
│   │                   │   ├── vml-main.xsd
│   │                   │   ├── vml-officeDrawing.xsd
│   │                   │   ├── vml-presentationDrawing.xsd
│   │                   │   ├── vml-spreadsheetDrawing.xsd
│   │                   │   ├── vml-wordprocessingDrawing.xsd
│   │                   │   ├── wml.xsd
│   │                   │   └── xml.xsd
│   │                   ├── ecma/
│   │                   │   └── fourth-edition/
│   │                   │       ├── opc-contentTypes.xsd
│   │                   │       ├── opc-coreProperties.xsd
│   │                   │       ├── opc-digSig.xsd
│   │                   │       └── opc-relationships.xsd
│   │                   ├── mce/
│   │                   │   └── mc.xsd
│   │                   └── microsoft/
│   │                       ├── wml-2010.xsd
│   │                       ├── wml-2012.xsd
│   │                       ├── wml-2018.xsd
│   │                       ├── wml-cex-2018.xsd
│   │                       ├── wml-cid-2016.xsd
│   │                       ├── wml-sdtdatahash-2020.xsd
│   │                       └── wml-symex-2015.xsd
│   ├── research/
│   │   ├── DESCRIPTION.md
│   │   ├── arxiv/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── search_arxiv.py
│   │   ├── blogwatcher/
│   │   │   └── SKILL.md
│   │   ├── domain-intel/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── domain_intel.py
│   │   ├── duckduckgo-search/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/
│   │   │       └── duckduckgo.sh
│   │   ├── ml-paper-writing/
│   │   │   ├── SKILL.md
│   │   │   ├── references/
│   │   │   │   ├── checklists.md
│   │   │   │   ├── citation-workflow.md
│   │   │   │   ├── reviewer-guidelines.md
│   │   │   │   ├── sources.md
│   │   │   │   └── writing-guide.md
│   │   │   └── templates/
│   │   │       ├── README.md
│   │   │       ├── aaai2026/
│   │   │       │   ├── README.md
│   │   │       │   ├── aaai2026-unified-supp.tex
│   │   │       │   ├── aaai2026-unified-template.tex
│   │   │       │   ├── aaai2026.bib
│   │   │       │   ├── aaai2026.bst
│   │   │       │   └── aaai2026.sty
│   │   │       ├── acl/
│   │   │       │   ├── README.md
│   │   │       │   ├── acl.sty
│   │   │       │   ├── acl_latex.tex
│   │   │       │   ├── acl_lualatex.tex
│   │   │       │   ├── acl_natbib.bst
│   │   │       │   ├── anthology.bib.txt
│   │   │       │   ├── custom.bib
│   │   │       │   └── formatting.md
│   │   │       ├── colm2025/
│   │   │       │   ├── README.md
│   │   │       │   ├── colm2025_conference.bib
│   │   │       │   ├── colm2025_conference.bst
│   │   │       │   ├── colm2025_conference.sty
│   │   │       │   ├── colm2025_conference.tex
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── math_commands.tex
│   │   │       │   └── natbib.sty
│   │   │       ├── iclr2026/
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── iclr2026_conference.bib
│   │   │       │   ├── iclr2026_conference.bst
│   │   │       │   ├── iclr2026_conference.sty
│   │   │       │   ├── iclr2026_conference.tex
│   │   │       │   ├── math_commands.tex
│   │   │       │   └── natbib.sty
│   │   │       ├── icml2026/
│   │   │       │   ├── algorithm.sty
│   │   │       │   ├── algorithmic.sty
│   │   │       │   ├── example_paper.bib
│   │   │       │   ├── example_paper.tex
│   │   │       │   ├── fancyhdr.sty
│   │   │       │   ├── icml2026.bst
│   │   │       │   └── icml2026.sty
│   │   │       └── neurips2025/
│   │   │           ├── Makefile
│   │   │           ├── extra_pkgs.tex
│   │   │           ├── main.tex
│   │   │           └── neurips.sty
│   │   ├── parallel-cli/
│   │   │   └── SKILL.md
│   │   └── polymarket/
│   │       ├── SKILL.md
│   │       ├── references/
│   │       │   └── api-endpoints.md
│   │       └── scripts/
│   │           └── polymarket.py
│   ├── smart-home/
│   │   ├── DESCRIPTION.md
│   │   └── openhue/
│   │       └── SKILL.md
│   ├── social-media/
│   │   ├── DESCRIPTION.md
│   │   └── xitter/
│   │       └── SKILL.md
│   └── software-development/
│       ├── code-review/
│       │   └── SKILL.md
│       ├── plan/
│       │   └── SKILL.md
│       ├── requesting-code-review/
│       │   └── SKILL.md
│       ├── subagent-driven-development/
│       │   └── SKILL.md
│       ├── systematic-debugging/
│       │   └── SKILL.md
│       ├── test-driven-development/
│       │   └── SKILL.md
│       └── writing-plans/
│           └── SKILL.md
├── tests/
│   ├── __init__.py
│   ├── acp/
│   │   ├── __init__.py
│   │   ├── test_auth.py
│   │   ├── test_events.py
│   │   ├── test_permissions.py
│   │   ├── test_server.py
│   │   ├── test_session.py
│   │   └── test_tools.py
│   ├── agent/
│   │   ├── __init__.py
│   │   ├── test_auxiliary_client.py
│   │   ├── test_context_compressor.py
│   │   ├── test_display_emoji.py
│   │   ├── test_model_metadata.py
│   │   ├── test_models_dev.py
│   │   ├── test_prompt_builder.py
│   │   ├── test_prompt_caching.py
│   │   ├── test_redact.py
│   │   ├── test_skill_commands.py
│   │   ├── test_smart_model_routing.py
│   │   ├── test_subagent_progress.py
│   │   ├── test_title_generator.py
│   │   └── test_usage_pricing.py
│   ├── conftest.py
│   ├── cron/
│   │   ├── __init__.py
│   │   ├── test_jobs.py
│   │   └── test_scheduler.py
│   ├── fakes/
│   │   ├── __init__.py
│   │   └── fake_ha_server.py
│   ├── gateway/
│   │   ├── __init__.py
│   │   ├── test_api_server.py
│   │   ├── test_approve_deny_commands.py
│   │   ├── test_async_memory_flush.py
│   │   ├── test_background_command.py
│   │   ├── test_background_process_notifications.py
│   │   ├── test_base_topic_sessions.py
│   │   ├── test_channel_directory.py
│   │   ├── test_config.py
│   │   ├── test_config_cwd_bridge.py
│   │   ├── test_delivery.py
│   │   ├── test_dingtalk.py
│   │   ├── test_discord_bot_filter.py
│   │   ├── test_discord_free_response.py
│   │   ├── test_discord_imports.py
│   │   ├── test_discord_media_metadata.py
│   │   ├── test_discord_opus.py
│   │   ├── test_discord_send.py
│   │   ├── test_discord_slash_commands.py
│   │   ├── test_discord_thread_persistence.py
│   │   ├── test_document_cache.py
│   │   ├── test_email.py
│   │   ├── test_extract_local_files.py
│   │   ├── test_gateway_shutdown.py
│   │   ├── test_homeassistant.py
│   │   ├── test_honcho_lifecycle.py
│   │   ├── test_hooks.py
│   │   ├── test_interrupt_key_match.py
│   │   ├── test_matrix.py
│   │   ├── test_mattermost.py
│   │   ├── test_media_extraction.py
│   │   ├── test_mirror.py
│   │   ├── test_pairing.py
│   │   ├── test_pii_redaction.py
│   │   ├── test_plan_command.py
│   │   ├── test_platform_base.py
│   │   ├── test_reasoning_command.py
│   │   ├── test_resume_command.py
│   │   ├── test_retry_replacement.py
│   │   ├── test_retry_response.py
│   │   ├── test_run_progress_topics.py
│   │   ├── test_runner_fatal_adapter.py
│   │   ├── test_runner_startup_failures.py
│   │   ├── test_send_image_file.py
│   │   ├── test_session.py
│   │   ├── test_session_env.py
│   │   ├── test_session_hygiene.py
│   │   ├── test_session_race_guard.py
│   │   ├── test_signal.py
│   │   ├── test_slack.py
│   │   ├── test_sms.py
│   │   ├── test_ssl_certs.py
│   │   ├── test_status.py
│   │   ├── test_status_command.py
│   │   ├── test_sticker_cache.py
│   │   ├── test_stt_config.py
│   │   ├── test_telegram_conflict.py
│   │   ├── test_telegram_documents.py
│   │   ├── test_telegram_format.py
│   │   ├── test_telegram_photo_interrupts.py
│   │   ├── test_telegram_text_batching.py
│   │   ├── test_title_command.py
│   │   ├── test_transcript_offset.py
│   │   ├── test_unauthorized_dm_behavior.py
│   │   ├── test_update_command.py
│   │   ├── test_voice_command.py
│   │   ├── test_webhook_adapter.py
│   │   ├── test_webhook_integration.py
│   │   ├── test_whatsapp_connect.py
│   │   └── test_whatsapp_reply_prefix.py
│   ├── hermes_cli/
│   │   ├── __init__.py
│   │   ├── test_banner.py
│   │   ├── test_banner_skills.py
│   │   ├── test_chat_skills_flag.py
│   │   ├── test_claw.py
│   │   ├── test_cmd_update.py
│   │   ├── test_coalesce_session_args.py
│   │   ├── test_commands.py
│   │   ├── test_config.py
│   │   ├── test_copilot_auth.py
│   │   ├── test_cron.py
│   │   ├── test_doctor.py
│   │   ├── test_env_loader.py
│   │   ├── test_gateway.py
│   │   ├── test_gateway_linger.py
│   │   ├── test_gateway_runtime_health.py
│   │   ├── test_gateway_service.py
│   │   ├── test_mcp_tools_config.py
│   │   ├── test_model_validation.py
│   │   ├── test_models.py
│   │   ├── test_path_completion.py
│   │   ├── test_placeholder_usage.py
│   │   ├── test_session_browse.py
│   │   ├── test_sessions_delete.py
│   │   ├── test_set_config_value.py
│   │   ├── test_setup.py
│   │   ├── test_setup_model_provider.py
│   │   ├── test_setup_noninteractive.py
│   │   ├── test_setup_openclaw_migration.py
│   │   ├── test_setup_prompt_menus.py
│   │   ├── test_skills_config.py
│   │   ├── test_skills_hub.py
│   │   ├── test_skills_install_flags.py
│   │   ├── test_skills_skip_confirm.py
│   │   ├── test_skills_subparser.py
│   │   ├── test_skin_engine.py
│   │   ├── test_status.py
│   │   ├── test_status_model_provider.py
│   │   ├── test_tools_config.py
│   │   ├── test_tools_disable_enable.py
│   │   ├── test_update_autostash.py
│   │   ├── test_update_check.py
│   │   └── test_update_gateway_restart.py
│   ├── honcho_integration/
│   │   ├── __init__.py
│   │   ├── test_async_memory.py
│   │   ├── test_cli.py
│   │   ├── test_client.py
│   │   └── test_session.py
│   ├── integration/
│   │   ├── __init__.py
│   │   ├── test_batch_runner.py
│   │   ├── test_checkpoint_resumption.py
│   │   ├── test_daytona_terminal.py
│   │   ├── test_ha_integration.py
│   │   ├── test_modal_terminal.py
│   │   ├── test_voice_channel_flow.py
│   │   └── test_web_tools.py
│   ├── run_interrupt_test.py
│   ├── skills/
│   │   ├── test_google_oauth_setup.py
│   │   ├── test_openclaw_migration.py
│   │   └── test_telephony_skill.py
│   ├── test_1630_context_overflow_loop.py
│   ├── test_413_compression.py
│   ├── test_860_dedup.py
│   ├── test_agent_guardrails.py
│   ├── test_agent_loop.py
│   ├── test_agent_loop_tool_calling.py
│   ├── test_agent_loop_vllm.py
│   ├── test_anthropic_adapter.py
│   ├── test_anthropic_error_handling.py
│   ├── test_anthropic_oauth_flow.py
│   ├── test_anthropic_provider_persistence.py
│   ├── test_api_key_providers.py
│   ├── test_atomic_json_write.py
│   ├── test_atomic_yaml_write.py
│   ├── test_auth_codex_provider.py
│   ├── test_auth_nous_provider.py
│   ├── test_auxiliary_config_bridge.py
│   ├── test_batch_runner_checkpoint.py
│   ├── test_cli_approval_ui.py
│   ├── test_cli_init.py
│   ├── test_cli_interrupt_subagent.py
│   ├── test_cli_loading_indicator.py
│   ├── test_cli_mcp_config_watch.py
│   ├── test_cli_model_command.py
│   ├── test_cli_new_session.py
│   ├── test_cli_plan_command.py
│   ├── test_cli_prefix_matching.py
│   ├── test_cli_preloaded_skills.py
│   ├── test_cli_provider_resolution.py
│   ├── test_cli_retry.py
│   ├── test_cli_secret_capture.py
│   ├── test_cli_skin_integration.py
│   ├── test_cli_status_bar.py
│   ├── test_cli_tools_command.py
│   ├── test_codex_execution_paths.py
│   ├── test_codex_models.py
│   ├── test_compression_boundary.py
│   ├── test_context_pressure.py
│   ├── test_context_token_tracking.py
│   ├── test_dict_tool_call_args.py
│   ├── test_display.py
│   ├── test_evidence_store.py
│   ├── test_external_credential_detection.py
│   ├── test_fallback_model.py
│   ├── test_file_permissions.py
│   ├── test_flush_memories_codex.py
│   ├── test_hermes_state.py
│   ├── test_honcho_client_config.py
│   ├── test_insights.py
│   ├── test_interactive_interrupt.py
│   ├── test_interrupt_propagation.py
│   ├── test_managed_server_tool_support.py
│   ├── test_minisweagent_path.py
│   ├── test_model_metadata_local_ctx.py
│   ├── test_model_provider_persistence.py
│   ├── test_model_tools.py
│   ├── test_model_tools_async_bridge.py
│   ├── test_openai_client_lifecycle.py
│   ├── test_personality_none.py
│   ├── test_plugins.py
│   ├── test_provider_parity.py
│   ├── test_quick_commands.py
│   ├── test_real_interrupt_subagent.py
│   ├── test_reasoning_command.py
│   ├── test_redirect_stdout_issue.py
│   ├── test_resume_display.py
│   ├── test_run_agent.py
│   ├── test_run_agent_codex_responses.py
│   ├── test_runtime_provider_resolution.py
│   ├── test_setup_model_selection.py
│   ├── test_sql_injection.py
│   ├── test_streaming.py
│   ├── test_timezone.py
│   ├── test_tool_call_parsers.py
│   ├── test_toolset_distributions.py
│   ├── test_toolsets.py
│   ├── test_trajectory_compressor.py
│   ├── test_worktree.py
│   ├── test_worktree_security.py
│   └── tools/
│       ├── __init__.py
│       ├── test_approval.py
│       ├── test_browser_cdp_override.py
│       ├── test_browser_cleanup.py
│       ├── test_browser_console.py
│       ├── test_checkpoint_manager.py
│       ├── test_clarify_tool.py
│       ├── test_clipboard.py
│       ├── test_code_execution.py
│       ├── test_command_guards.py
│       ├── test_cron_prompt_injection.py
│       ├── test_cronjob_tools.py
│       ├── test_daytona_environment.py
│       ├── test_debug_helpers.py
│       ├── test_delegate.py
│       ├── test_docker_environment.py
│       ├── test_docker_find.py
│       ├── test_file_operations.py
│       ├── test_file_tools.py
│       ├── test_file_tools_live.py
│       ├── test_file_write_safety.py
│       ├── test_force_dangerous_override.py
│       ├── test_fuzzy_match.py
│       ├── test_hidden_dir_filter.py
│       ├── test_homeassistant_tool.py
│       ├── test_honcho_tools.py
│       ├── test_interrupt.py
│       ├── test_local_env_blocklist.py
│       ├── test_local_persistent.py
│       ├── test_mcp_probe.py
│       ├── test_mcp_tool.py
│       ├── test_mcp_tool_issue_948.py
│       ├── test_memory_tool.py
│       ├── test_mixture_of_agents_tool.py
│       ├── test_modal_sandbox_fixes.py
│       ├── test_parse_env_var.py
│       ├── test_patch_parser.py
│       ├── test_process_registry.py
│       ├── test_read_loop_detection.py
│       ├── test_registry.py
│       ├── test_rl_training_tool.py
│       ├── test_search_hidden_dirs.py
│       ├── test_send_message_tool.py
│       ├── test_session_search.py
│       ├── test_singularity_preflight.py
│       ├── test_skill_manager_tool.py
│       ├── test_skill_view_path_check.py
│       ├── test_skill_view_traversal.py
│       ├── test_skills_guard.py
│       ├── test_skills_hub.py
│       ├── test_skills_hub_clawhub.py
│       ├── test_skills_sync.py
│       ├── test_skills_tool.py
│       ├── test_ssh_environment.py
│       ├── test_symlink_prefix_confusion.py
│       ├── test_terminal_disk_usage.py
│       ├── test_terminal_requirements.py
│       ├── test_terminal_tool_requirements.py
│       ├── test_tirith_security.py
│       ├── test_todo_tool.py
│       ├── test_transcription.py
│       ├── test_transcription_tools.py
│       ├── test_vision_tools.py
│       ├── test_voice_cli_integration.py
│       ├── test_voice_mode.py
│       ├── test_web_tools_config.py
│       ├── test_web_tools_tavily.py
│       ├── test_website_policy.py
│       ├── test_windows_compat.py
│       ├── test_write_deny.py
│       └── test_yolo_mode.py
├── tools/
│   ├── __init__.py
│   ├── approval.py
│   ├── browser_providers/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── browser_use.py
│   │   └── browserbase.py
│   ├── browser_tool.py
│   ├── checkpoint_manager.py
│   ├── clarify_tool.py
│   ├── code_execution_tool.py
│   ├── cronjob_tools.py
│   ├── debug_helpers.py
│   ├── delegate_tool.py
│   ├── environments/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── daytona.py
│   │   ├── docker.py
│   │   ├── local.py
│   │   ├── modal.py
│   │   ├── persistent_shell.py
│   │   ├── singularity.py
│   │   └── ssh.py
│   ├── file_operations.py
│   ├── file_tools.py
│   ├── fuzzy_match.py
│   ├── homeassistant_tool.py
│   ├── honcho_tools.py
│   ├── image_generation_tool.py
│   ├── interrupt.py
│   ├── mcp_tool.py
│   ├── memory_tool.py
│   ├── mixture_of_agents_tool.py
│   ├── neutts_samples/
│   │   └── jo.txt
│   ├── neutts_synth.py
│   ├── openrouter_client.py
│   ├── patch_parser.py
│   ├── process_registry.py
│   ├── registry.py
│   ├── rl_training_tool.py
│   ├── send_message_tool.py
│   ├── session_search_tool.py
│   ├── skill_manager_tool.py
│   ├── skills_guard.py
│   ├── skills_hub.py
│   ├── skills_sync.py
│   ├── skills_tool.py
│   ├── terminal_tool.py
│   ├── tirith_security.py
│   ├── todo_tool.py
│   ├── transcription_tools.py
│   ├── tts_tool.py
│   ├── vision_tools.py
│   ├── voice_mode.py
│   ├── web_tools.py
│   └── website_policy.py
├── toolset_distributions.py
├── toolsets.py
├── trajectory_compressor.py
├── utils.py
└── website/
    ├── .gitignore
    ├── README.md
    ├── docs/
    │   ├── developer-guide/
    │   │   ├── _category_.json
    │   │   ├── acp-internals.md
    │   │   ├── adding-providers.md
    │   │   ├── adding-tools.md
    │   │   ├── agent-loop.md
    │   │   ├── architecture.md
    │   │   ├── context-compression-and-caching.md
    │   │   ├── contributing.md
    │   │   ├── creating-skills.md
    │   │   ├── cron-internals.md
    │   │   ├── environments.md
    │   │   ├── gateway-internals.md
    │   │   ├── prompt-assembly.md
    │   │   ├── provider-runtime.md
    │   │   ├── session-storage.md
    │   │   ├── tools-runtime.md
    │   │   └── trajectory-format.md
    │   ├── getting-started/
    │   │   ├── _category_.json
    │   │   ├── installation.md
    │   │   ├── learning-path.md
    │   │   ├── quickstart.md
    │   │   └── updating.md
    │   ├── guides/
    │   │   ├── _category_.json
    │   │   ├── build-a-hermes-plugin.md
    │   │   ├── daily-briefing-bot.md
    │   │   ├── python-library.md
    │   │   ├── team-telegram-assistant.md
    │   │   ├── tips.md
    │   │   ├── use-mcp-with-hermes.md
    │   │   ├── use-soul-with-hermes.md
    │   │   └── use-voice-mode-with-hermes.md
    │   ├── index.md
    │   ├── reference/
    │   │   ├── _category_.json
    │   │   ├── cli-commands.md
    │   │   ├── environment-variables.md
    │   │   ├── faq.md
    │   │   ├── mcp-config-reference.md
    │   │   ├── optional-skills-catalog.md
    │   │   ├── skills-catalog.md
    │   │   ├── slash-commands.md
    │   │   ├── tools-reference.md
    │   │   └── toolsets-reference.md
    │   └── user-guide/
    │       ├── _category_.json
    │       ├── checkpoints-and-rollback.md
    │       ├── cli.md
    │       ├── configuration.md
    │       ├── features/
    │       │   ├── _category_.json
    │       │   ├── acp.md
    │       │   ├── api-server.md
    │       │   ├── batch-processing.md
    │       │   ├── browser.md
    │       │   ├── checkpoints.md
    │       │   ├── code-execution.md
    │       │   ├── context-files.md
    │       │   ├── cron.md
    │       │   ├── delegation.md
    │       │   ├── fallback-providers.md
    │       │   ├── honcho.md
    │       │   ├── hooks.md
    │       │   ├── image-generation.md
    │       │   ├── mcp.md
    │       │   ├── memory.md
    │       │   ├── personality.md
    │       │   ├── plugins.md
    │       │   ├── provider-routing.md
    │       │   ├── rl-training.md
    │       │   ├── skills.md
    │       │   ├── skins.md
    │       │   ├── tools.md
    │       │   ├── tts.md
    │       │   ├── vision.md
    │       │   └── voice-mode.md
    │       ├── git-worktrees.md
    │       ├── messaging/
    │       │   ├── _category_.json
    │       │   ├── dingtalk.md
    │       │   ├── discord.md
    │       │   ├── email.md
    │       │   ├── homeassistant.md
    │       │   ├── index.md
    │       │   ├── matrix.md
    │       │   ├── mattermost.md
    │       │   ├── open-webui.md
    │       │   ├── signal.md
    │       │   ├── slack.md
    │       │   ├── sms.md
    │       │   ├── telegram.md
    │       │   ├── webhooks.md
    │       │   └── whatsapp.md
    │       ├── security.md
    │       └── sessions.md
    ├── docusaurus.config.ts
    ├── package.json
    ├── sidebars.ts
    ├── src/
    │   └── css/
    │       └── custom.css
    ├── static/
    │   └── .nojekyll
    └── tsconfig.json
Download .txt
Showing preview only (1,080K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (11130 symbols across 497 files)

FILE: acp_adapter/auth.py
  function detect_provider (line 8) | def detect_provider() -> Optional[str]:
  function has_provider (line 22) | def has_provider() -> bool:

FILE: acp_adapter/entry.py
  function _setup_logging (line 23) | def _setup_logging() -> None:
  function _load_env (line 43) | def _load_env() -> None:
  function main (line 58) | def main() -> None:

FILE: acp_adapter/events.py
  function _send_update (line 27) | def _send_update(
  function make_tool_progress_cb (line 47) | def make_tool_progress_cb(
  function make_thinking_cb (line 93) | def make_thinking_cb(
  function make_step_cb (line 113) | def make_step_cb(
  function make_message_cb (line 158) | def make_message_cb(

FILE: acp_adapter/permissions.py
  function make_approval_callback (line 29) | def make_approval_callback(

FILE: acp_adapter/server.py
  function _extract_text (line 58) | def _extract_text(
  class HermesACPAgent (line 78) | class HermesACPAgent(acp.Agent):
    method __init__ (line 81) | def __init__(self, session_manager: SessionManager | None = None):
    method on_connect (line 88) | def on_connect(self, conn: acp.Client) -> None:
    method initialize (line 95) | async def initialize(
    method authenticate (line 128) | async def authenticate(self, method_id: str, **kwargs: Any) -> Authent...
    method new_session (line 135) | async def new_session(
    method load_session (line 145) | async def load_session(
    method resume_session (line 159) | async def resume_session(
    method cancel (line 173) | async def cancel(self, session_id: str, **kwargs: Any) -> None:
    method fork_session (line 184) | async def fork_session(
    method list_sessions (line 196) | async def list_sessions(
    method prompt (line 211) | async def prompt(
    method _handle_slash_command (line 341) | def _handle_slash_command(self, text: str, state: SessionState) -> str...
    method _cmd_help (line 370) | def _cmd_help(self, args: str, state: SessionState) -> str:
    method _cmd_model (line 378) | def _cmd_model(self, args: str, state: SessionState) -> str:
    method _cmd_tools (line 410) | def _cmd_tools(self, args: str, state: SessionState) -> str:
    method _cmd_context (line 429) | def _cmd_context(self, args: str, state: SessionState) -> str:
    method _cmd_reset (line 448) | def _cmd_reset(self, args: str, state: SessionState) -> str:
    method _cmd_compact (line 453) | def _cmd_compact(self, args: str, state: SessionState) -> str:
    method _cmd_version (line 466) | def _cmd_version(self, args: str, state: SessionState) -> str:
    method set_session_model (line 471) | async def set_session_model(

FILE: acp_adapter/session.py
  function _register_task_cwd (line 22) | def _register_task_cwd(task_id: str, cwd: str) -> None:
  function _clear_task_cwd (line 33) | def _clear_task_cwd(task_id: str) -> None:
  class SessionState (line 45) | class SessionState:
  class SessionManager (line 56) | class SessionManager:
    method __init__ (line 64) | def __init__(self, agent_factory=None, db=None):
    method create_session (line 80) | def create_session(self, cwd: str = ".") -> SessionState:
    method get_session (line 100) | def get_session(self, session_id: str) -> Optional[SessionState]:
    method remove_session (line 113) | def remove_session(self, session_id: str) -> bool:
    method fork_session (line 122) | def fork_session(self, session_id: str, cwd: str = ".") -> Optional[Se...
    method list_sessions (line 151) | def list_sessions(self) -> List[Dict[str, Any]]:
    method update_cwd (line 194) | def update_cwd(self, session_id: str, cwd: str) -> Optional[SessionSta...
    method cleanup (line 204) | def cleanup(self) -> None:
    method save_session (line 224) | def save_session(self, session_id: str) -> None:
    method _get_db (line 237) | def _get_db(self):
    method _persist (line 261) | def _persist(self, state: SessionState) -> None:
    method _restore (line 311) | def _restore(self, session_id: str) -> Optional[SessionState]:
    method _delete_persisted (line 370) | def _delete_persisted(self, session_id: str) -> bool:
    method _make_agent (line 383) | def _make_agent(

FILE: acp_adapter/tools.py
  function get_tool_kind (line 54) | def get_tool_kind(tool_name: str) -> ToolKind:
  function make_tool_call_id (line 59) | def make_tool_call_id() -> str:
  function build_tool_title (line 64) | def build_tool_title(tool_name: str, args: Dict[str, Any]) -> str:
  function build_tool_start (line 105) | def build_tool_start(
  function build_tool_complete (line 178) | def build_tool_complete(
  function extract_locations (line 206) | def extract_locations(

FILE: agent/anthropic_adapter.py
  function _supports_adaptive_thinking (line 37) | def _supports_adaptive_thinking(model: str) -> bool:
  function _detect_claude_code_version (line 62) | def _detect_claude_code_version() -> str:
  function _is_oauth_token (line 92) | def _is_oauth_token(key: str) -> bool:
  function build_anthropic_client (line 107) | def build_anthropic_client(api_key: str, base_url: str = None):
  function read_claude_code_credentials (line 145) | def read_claude_code_credentials() -> Optional[Dict[str, Any]]:
  function read_claude_managed_key (line 175) | def read_claude_managed_key() -> Optional[str]:
  function is_claude_code_token_valid (line 189) | def is_claude_code_token_valid(creds: Dict[str, Any]) -> bool:
  function _refresh_oauth_token (line 204) | def _refresh_oauth_token(creds: Dict[str, Any]) -> Optional[str]:
  function _write_claude_code_credentials (line 260) | def _write_claude_code_credentials(access_token: str, refresh_token: str...
  function _resolve_claude_code_token_from_credentials (line 283) | def _resolve_claude_code_token_from_credentials(creds: Optional[Dict[str...
  function _prefer_refreshable_claude_code_token (line 298) | def _prefer_refreshable_claude_code_token(env_token: str, creds: Optiona...
  function get_anthropic_token_source (line 320) | def get_anthropic_token_source(token: Optional[str] = None) -> str:
  function resolve_anthropic_token (line 349) | def resolve_anthropic_token() -> Optional[str]:
  function run_oauth_setup_token (line 405) | def run_oauth_setup_token() -> Optional[str]:
  function _generate_pkce (line 456) | def _generate_pkce() -> tuple:
  function run_hermes_oauth_login (line 469) | def run_hermes_oauth_login() -> Optional[str]:
  function _save_hermes_oauth_credentials (line 578) | def _save_hermes_oauth_credentials(access_token: str, refresh_token: str...
  function read_hermes_oauth_credentials (line 593) | def read_hermes_oauth_credentials() -> Optional[Dict[str, Any]]:
  function refresh_hermes_oauth_token (line 605) | def refresh_hermes_oauth_token() -> Optional[str]:
  function normalize_model_name (line 659) | def normalize_model_name(model: str) -> str:
  function _sanitize_tool_id (line 675) | def _sanitize_tool_id(tool_id: str) -> str:
  function _convert_openai_image_part_to_anthropic (line 688) | def _convert_openai_image_part_to_anthropic(part: Dict[str, Any]) -> Opt...
  function _convert_user_content_part_to_anthropic (line 721) | def _convert_user_content_part_to_anthropic(part: Any) -> Optional[Dict[...
  function convert_tools_to_anthropic (line 750) | def convert_tools_to_anthropic(tools: List[Dict]) -> List[Dict]:
  function _image_source_from_openai_url (line 765) | def _image_source_from_openai_url(url: str) -> Dict[str, str]:
  function _convert_content_part_to_anthropic (line 787) | def _convert_content_part_to_anthropic(part: Any) -> Optional[Dict[str, ...
  function _convert_content_to_anthropic (line 812) | def _convert_content_to_anthropic(content: Any) -> Any:
  function convert_messages_to_anthropic (line 825) | def convert_messages_to_anthropic(
  function build_anthropic_kwargs (line 1001) | def build_anthropic_kwargs(
  function normalize_anthropic_response (line 1106) | def normalize_anthropic_response(

FILE: agent/auxiliary_client.py
  function _convert_content_for_responses (line 104) | def _convert_content_for_responses(content: Any) -> Any:
  class _CodexCompletionsAdapter (line 152) | class _CodexCompletionsAdapter:
    method __init__ (line 156) | def __init__(self, real_client: OpenAI, model: str):
    method create (line 160) | def create(self, **kwargs) -> Any:
  class _CodexChatShim (line 269) | class _CodexChatShim:
    method __init__ (line 272) | def __init__(self, adapter: _CodexCompletionsAdapter):
  class CodexAuxiliaryClient (line 276) | class CodexAuxiliaryClient:
    method __init__ (line 283) | def __init__(self, real_client: OpenAI, model: str):
    method close (line 290) | def close(self):
  class _AsyncCodexCompletionsAdapter (line 294) | class _AsyncCodexCompletionsAdapter:
    method __init__ (line 301) | def __init__(self, sync_adapter: _CodexCompletionsAdapter):
    method create (line 304) | async def create(self, **kwargs) -> Any:
  class _AsyncCodexChatShim (line 309) | class _AsyncCodexChatShim:
    method __init__ (line 310) | def __init__(self, adapter: _AsyncCodexCompletionsAdapter):
  class AsyncCodexAuxiliaryClient (line 314) | class AsyncCodexAuxiliaryClient:
    method __init__ (line 317) | def __init__(self, sync_wrapper: "CodexAuxiliaryClient"):
  class _AnthropicCompletionsAdapter (line 325) | class _AnthropicCompletionsAdapter:
    method __init__ (line 328) | def __init__(self, real_client: Any, model: str):
    method create (line 332) | def create(self, **kwargs) -> Any:
  class _AnthropicChatShim (line 389) | class _AnthropicChatShim:
    method __init__ (line 390) | def __init__(self, adapter: _AnthropicCompletionsAdapter):
  class AnthropicAuxiliaryClient (line 394) | class AnthropicAuxiliaryClient:
    method __init__ (line 397) | def __init__(self, real_client: Any, model: str, api_key: str, base_ur...
    method close (line 404) | def close(self):
  class _AsyncAnthropicCompletionsAdapter (line 410) | class _AsyncAnthropicCompletionsAdapter:
    method __init__ (line 411) | def __init__(self, sync_adapter: _AnthropicCompletionsAdapter):
    method create (line 414) | async def create(self, **kwargs) -> Any:
  class _AsyncAnthropicChatShim (line 419) | class _AsyncAnthropicChatShim:
    method __init__ (line 420) | def __init__(self, adapter: _AsyncAnthropicCompletionsAdapter):
  class AsyncAnthropicAuxiliaryClient (line 424) | class AsyncAnthropicAuxiliaryClient:
    method __init__ (line 425) | def __init__(self, sync_wrapper: "AnthropicAuxiliaryClient"):
  function _read_nous_auth (line 433) | def _read_nous_auth() -> Optional[dict]:
  function _nous_api_key (line 455) | def _nous_api_key(provider: dict) -> str:
  function _nous_base_url (line 460) | def _nous_base_url() -> str:
  function _read_codex_access_token (line 465) | def _read_codex_access_token() -> Optional[str]:
  function _resolve_api_key_provider (line 480) | def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]:
  function _get_auxiliary_provider (line 520) | def _get_auxiliary_provider(task: str = "") -> str:
  function _get_auxiliary_env_override (line 535) | def _get_auxiliary_env_override(task: str, suffix: str) -> Optional[str]:
  function _try_openrouter (line 546) | def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:
  function _try_nous (line 555) | def _try_nous() -> Tuple[Optional[OpenAI], Optional[str]]:
  function _read_main_model (line 568) | def _read_main_model() -> str:
  function _resolve_custom_runtime (line 593) | def _resolve_custom_runtime() -> Tuple[Optional[str], Optional[str]]:
  function _current_custom_base_url (line 624) | def _current_custom_base_url() -> str:
  function _try_custom_endpoint (line 629) | def _try_custom_endpoint() -> Tuple[Optional[OpenAI], Optional[str]]:
  function _try_codex (line 638) | def _try_codex() -> Tuple[Optional[Any], Optional[str]]:
  function _try_anthropic (line 647) | def _try_anthropic() -> Tuple[Optional[Any], Optional[str]]:
  function _resolve_forced_provider (line 676) | def _resolve_forced_provider(forced: str) -> Tuple[Optional[OpenAI], Opt...
  function _resolve_auto (line 710) | def _resolve_auto() -> Tuple[Optional[OpenAI], Optional[str]]:
  function _to_async_client (line 734) | def _to_async_client(sync_client, model: str):
  function resolve_provider_client (line 759) | def resolve_provider_client(
  function get_text_auxiliary_client (line 963) | def get_text_auxiliary_client(task: str = "") -> Tuple[Optional[OpenAI],...
  function get_async_text_auxiliary_client (line 982) | def get_async_text_auxiliary_client(task: str = ""):
  function _normalize_vision_provider (line 1008) | def _normalize_vision_provider(provider: Optional[str]) -> str:
  function _resolve_strict_vision_backend (line 1017) | def _resolve_strict_vision_backend(provider: str) -> Tuple[Optional[Any]...
  function _strict_vision_backend_available (line 1032) | def _strict_vision_backend_available(provider: str) -> bool:
  function _preferred_main_vision_provider (line 1036) | def _preferred_main_vision_provider() -> Optional[str]:
  function get_available_vision_backends (line 1052) | def get_available_vision_backends() -> List[str]:
  function resolve_vision_provider_client (line 1068) | def resolve_vision_provider_client(
  function get_vision_auxiliary_client (line 1127) | def get_vision_auxiliary_client() -> Tuple[Optional[OpenAI], Optional[st...
  function get_async_vision_auxiliary_client (line 1133) | def get_async_vision_auxiliary_client():
  function get_auxiliary_extra_body (line 1139) | def get_auxiliary_extra_body() -> dict:
  function auxiliary_max_tokens_param (line 1148) | def auxiliary_max_tokens_param(value: int) -> dict:
  function _get_cached_client (line 1183) | def _get_cached_client(
  function _resolve_task_provider_model (line 1232) | def _resolve_task_provider_model(
  function _build_call_kwargs (line 1311) | def _build_call_kwargs(
  function call_llm (line 1357) | def call_llm(
  function async_call_llm (line 1459) | async def async_call_llm(

FILE: agent/context_compressor.py
  class ContextCompressor (line 31) | class ContextCompressor:
    method __init__ (line 38) | def __init__(
    method update_from_response (line 77) | def update_from_response(self, usage: Dict[str, Any]):
    method should_compress (line 83) | def should_compress(self, prompt_tokens: int = None) -> bool:
    method should_compress_preflight (line 88) | def should_compress_preflight(self, messages: List[Dict[str, Any]]) ->...
    method get_status (line 93) | def get_status(self) -> Dict[str, Any]:
    method _generate_summary (line 103) | def _generate_summary(self, turns_to_summarize: List[Dict[str, Any]]) ...
    method _with_summary_prefix (line 169) | def _with_summary_prefix(summary: str) -> str:
    method _get_tool_call_id (line 183) | def _get_tool_call_id(tc) -> str:
    method _sanitize_tool_pairs (line 189) | def _sanitize_tool_pairs(self, messages: List[Dict[str, Any]]) -> List...
    method _align_boundary_forward (line 249) | def _align_boundary_forward(self, messages: List[Dict[str, Any]], idx:...
    method _align_boundary_backward (line 259) | def _align_boundary_backward(self, messages: List[Dict[str, Any]], idx...
    method compress (line 283) | def compress(self, messages: List[Dict[str, Any]], current_tokens: int...

FILE: agent/copilot_acp_client.py
  function _resolve_command (line 27) | def _resolve_command() -> str:
  function _resolve_args (line 35) | def _resolve_args() -> list[str]:
  function _jsonrpc_error (line 42) | def _jsonrpc_error(message_id: Any, code: int, message: str) -> dict[str...
  function _format_messages_as_prompt (line 53) | def _format_messages_as_prompt(messages: list[dict[str, Any]], model: st...
  function _render_message_content (line 93) | def _render_message_content(content: Any) -> str:
  function _ensure_path_within_cwd (line 117) | def _ensure_path_within_cwd(path_text: str, cwd: str) -> Path:
  class _ACPChatCompletions (line 130) | class _ACPChatCompletions:
    method __init__ (line 131) | def __init__(self, client: "CopilotACPClient"):
    method create (line 134) | def create(self, **kwargs: Any) -> Any:
  class _ACPChatNamespace (line 138) | class _ACPChatNamespace:
    method __init__ (line 139) | def __init__(self, client: "CopilotACPClient"):
  class CopilotACPClient (line 143) | class CopilotACPClient:
    method __init__ (line 146) | def __init__(
    method close (line 170) | def close(self) -> None:
    method _create_chat_completion (line 187) | def _create_chat_completion(
    method _run_prompt (line 221) | def _run_prompt(self, prompt_text: str, *, timeout_seconds: float) -> ...
    method _handle_server_message (line 363) | def _handle_server_message(

FILE: agent/display.py
  function _get_skin (line 25) | def _get_skin():
  function get_skin_faces (line 34) | def get_skin_faces(key: str, default: list) -> list:
  function get_skin_verbs (line 44) | def get_skin_verbs() -> list:
  function get_skin_tool_prefix (line 54) | def get_skin_tool_prefix() -> str:
  function get_tool_emoji (line 62) | def get_tool_emoji(tool_name: str, default: str = "⚡") -> str:
  function _oneline (line 92) | def _oneline(text: str) -> str:
  function build_tool_preview (line 97) | def build_tool_preview(tool_name: str, args: dict, max_len: int = 40) ->...
  class KawaiiSpinner (line 202) | class KawaiiSpinner:
    method __init__ (line 234) | def __init__(self, message: str = "", spinner_type: str = 'dots'):
    method _write (line 247) | def _write(self, text: str, end: str = '\n', flush: bool = False):
    method _animate (line 256) | def _animate(self):
    method start (line 298) | def start(self):
    method update_text (line 306) | def update_text(self, new_message: str):
    method print_above (line 309) | def print_above(self, text: str):
    method stop (line 327) | def stop(self, final_message: str = None):
    method __enter__ (line 345) | def __enter__(self):
    method __exit__ (line 349) | def __exit__(self, exc_type, exc_val, exc_tb):
  function _detect_tool_failure (line 398) | def _detect_tool_failure(tool_name: str, result: str | None) -> tuple[bo...
  function get_cute_tool_message (line 435) | def get_cute_tool_message(
  function honcho_session_url (line 599) | def honcho_session_url(workspace: str, session_name: str) -> str:
  function _osc8_link (line 610) | def _osc8_link(url: str, text: str) -> str:
  function honcho_session_line (line 615) | def honcho_session_line(workspace: str, session_name: str) -> str:
  function write_tty (line 622) | def write_tty(text: str) -> None:
  function format_context_pressure (line 649) | def format_context_pressure(
  function format_context_pressure_gateway (line 696) | def format_context_pressure_gateway(

FILE: agent/insights.py
  function _has_known_pricing (line 37) | def _has_known_pricing(model_name: str, provider: str = None, base_url: ...
  function _get_pricing (line 42) | def _get_pricing(model_name: str) -> Dict[str, float]:
  function _estimate_cost (line 51) | def _estimate_cost(
  function _format_duration (line 90) | def _format_duration(seconds: float) -> str:
  function _bar_chart (line 95) | def _bar_chart(values: List[int], max_width: int = 20) -> List[str]:
  class InsightsEngine (line 103) | class InsightsEngine:
    method __init__ (line 111) | def __init__(self, db):
    method generate (line 121) | def generate(self, days: int = 30, source: str = None) -> Dict[str, Any]:
    method _get_sessions (line 197) | def _get_sessions(self, cutoff: float, source: str = None) -> List[Dict]:
    method _get_tool_usage (line 205) | def _get_tool_usage(self, cutoff: float, source: str = None) -> List[D...
    method _get_message_stats (line 297) | def _get_message_stats(self, cutoff: float, source: str = None) -> Dict:
    method _compute_overview (line 333) | def _compute_overview(self, sessions: List[Dict], message_stats: Dict)...
    method _compute_model_breakdown (line 407) | def _compute_model_breakdown(self, sessions: List[Dict]) -> List[Dict]:
    method _compute_platform_breakdown (line 444) | def _compute_platform_breakdown(self, sessions: List[Dict]) -> List[Di...
    method _compute_tool_breakdown (line 475) | def _compute_tool_breakdown(self, tool_usage: List[Dict]) -> List[Dict]:
    method _compute_activity_patterns (line 488) | def _compute_activity_patterns(self, sessions: List[Dict]) -> Dict:
    method _compute_top_sessions (line 546) | def _compute_top_sessions(self, sessions: List[Dict]) -> List[Dict]:
    method format_terminal (line 608) | def format_terminal(self, report: Dict) -> str:
    method format_gateway (line 735) | def format_gateway(self, report: Dict) -> str:

FILE: agent/model_metadata.py
  function _strip_provider_prefix (line 43) | def _strip_provider_prefix(model: str) -> str:
  function _normalize_base_url (line 141) | def _normalize_base_url(base_url: str) -> str:
  function _is_openrouter_base_url (line 145) | def _is_openrouter_base_url(base_url: str) -> bool:
  function _is_custom_endpoint (line 149) | def _is_custom_endpoint(base_url: str) -> bool:
  function _infer_provider_from_url (line 170) | def _infer_provider_from_url(base_url: str) -> Optional[str]:
  function _is_known_provider_base_url (line 188) | def _is_known_provider_base_url(base_url: str) -> bool:
  function is_local_endpoint (line 192) | def is_local_endpoint(base_url: str) -> bool:
  function detect_local_server_type (line 228) | def detect_local_server_type(base_url: str) -> Optional[str]:
  function _iter_nested_dicts (line 285) | def _iter_nested_dicts(value: Any):
  function _coerce_reasonable_int (line 295) | def _coerce_reasonable_int(value: Any, minimum: int = 1024, maximum: int...
  function _extract_first_int (line 309) | def _extract_first_int(payload: Dict[str, Any], keys: tuple[str, ...]) -...
  function _extract_context_length (line 321) | def _extract_context_length(payload: Dict[str, Any]) -> Optional[int]:
  function _extract_max_completion_tokens (line 325) | def _extract_max_completion_tokens(payload: Dict[str, Any]) -> Optional[...
  function _extract_pricing (line 329) | def _extract_pricing(payload: Dict[str, Any]) -> Dict[str, Any]:
  function _add_model_aliases (line 352) | def _add_model_aliases(cache: Dict[str, Dict[str, Any]], model_id: str, ...
  function fetch_model_metadata (line 359) | def fetch_model_metadata(force_refresh: bool = False) -> Dict[str, Dict[...
  function fetch_endpoint_model_metadata (line 395) | def fetch_endpoint_model_metadata(
  function _get_context_cache_path (line 483) | def _get_context_cache_path() -> Path:
  function _load_context_cache (line 489) | def _load_context_cache() -> Dict[str, int]:
  function save_context_length (line 503) | def save_context_length(model: str, base_url: str, length: int) -> None:
  function get_cached_context_length (line 524) | def get_cached_context_length(model: str, base_url: str) -> Optional[int]:
  function get_next_probe_tier (line 531) | def get_next_probe_tier(current_length: int) -> Optional[int]:
  function parse_context_limit_from_error (line 539) | def parse_context_limit_from_error(error_msg: str) -> Optional[int]:
  function _model_id_matches (line 567) | def _model_id_matches(candidate_id: str, lookup_model: str) -> bool:
  function _query_local_context_length (line 586) | def _query_local_context_length(model: str, base_url: str) -> Optional[i...
  function _normalize_model_version (line 676) | def _normalize_model_version(model: str) -> str:
  function _query_anthropic_context_length (line 686) | def _query_anthropic_context_length(model: str, base_url: str, api_key: ...
  function _resolve_nous_context_length (line 717) | def _resolve_nous_context_length(model: str) -> Optional[int]:
  function get_model_context_length (line 750) | def get_model_context_length(
  function estimate_tokens_rough (line 875) | def estimate_tokens_rough(text: str) -> int:
  function estimate_messages_tokens_rough (line 882) | def estimate_messages_tokens_rough(messages: List[Dict[str, Any]]) -> int:

FILE: agent/models_dev.py
  function _get_cache_path (line 47) | def _get_cache_path() -> Path:
  function _load_disk_cache (line 54) | def _load_disk_cache() -> Dict[str, Any]:
  function _save_disk_cache (line 66) | def _save_disk_cache(data: Dict[str, Any]) -> None:
  function fetch_models_dev (line 77) | def fetch_models_dev(force_refresh: bool = False) -> Dict[str, Any]:
  function lookup_models_dev_context (line 121) | def lookup_models_dev_context(provider: str, model: str) -> Optional[int]:
  function _extract_context (line 158) | def _extract_context(entry: Dict[str, Any]) -> Optional[int]:

FILE: agent/prompt_builder.py
  function _scan_context_content (line 39) | def _scan_context_content(content: str, filename: str) -> str:
  function _find_git_root (line 60) | def _find_git_root(start: Path) -> Optional[Path]:
  function _find_hermes_md (line 76) | def _find_hermes_md(cwd: Path) -> Optional[Path]:
  function _strip_yaml_frontmatter (line 97) | def _strip_yaml_frontmatter(content: str) -> str:
  function _parse_skill_file (line 235) | def _parse_skill_file(skill_file: Path) -> tuple[bool, dict, str]:
  function _read_skill_conditions (line 263) | def _read_skill_conditions(skill_file: Path) -> dict:
  function _skill_should_show (line 281) | def _skill_should_show(
  function build_skills_system_prompt (line 312) | def build_skills_system_prompt(
  function _truncate_content (line 420) | def _truncate_content(content: str, filename: str, max_chars: int = CONT...
  function load_soul_md (line 432) | def load_soul_md() -> Optional[str]:
  function build_context_files_prompt (line 460) | def build_context_files_prompt(cwd: Optional[str] = None, skip_soul: boo...

FILE: agent/prompt_caching.py
  function _apply_cache_marker (line 15) | def _apply_cache_marker(msg: dict, cache_marker: dict) -> None:
  function apply_anthropic_cache_control (line 40) | def apply_anthropic_cache_control(

FILE: agent/redact.py
  function _mask_token (line 90) | def _mask_token(token: str) -> str:
  function redact_sensitive_text (line 97) | def redact_sensitive_text(text: str) -> str:
  class RedactingFormatter (line 153) | class RedactingFormatter(logging.Formatter):
    method __init__ (line 156) | def __init__(self, fmt=None, datefmt=None, style='%', **kwargs):
    method format (line 159) | def format(self, record: logging.LogRecord) -> str:

FILE: agent/skill_commands.py
  function build_plan_path (line 21) | def build_plan_path(
  function _load_skill_payload (line 42) | def _load_skill_payload(skill_identifier: str, task_id: str | None = Non...
  function _build_skill_message (line 79) | def _build_skill_message(
  function scan_skill_commands (line 151) | def scan_skill_commands() -> Dict[str, Dict[str, Any]]:
  function get_skill_commands (line 198) | def get_skill_commands() -> Dict[str, Dict[str, Any]]:
  function build_skill_invocation_message (line 205) | def build_skill_invocation_message(
  function build_preloaded_skills_prompt (line 243) | def build_preloaded_skills_prompt(

FILE: agent/smart_model_routing.py
  function _coerce_bool (line 49) | def _coerce_bool(value: Any, default: bool = False) -> bool:
  function _coerce_int (line 59) | def _coerce_int(value: Any, default: int) -> int:
  function choose_cheap_model_route (line 66) | def choose_cheap_model_route(user_message: str, routing_config: Optional...
  function resolve_turn_route (line 114) | def resolve_turn_route(user_message: str, routing_config: Optional[Dict[...

FILE: agent/title_generator.py
  function generate_title (line 22) | def generate_title(user_message: str, assistant_response: str, timeout: ...
  function auto_title_session (line 59) | def auto_title_session(
  function maybe_auto_title (line 95) | def maybe_auto_title(

FILE: agent/trajectory.py
  function convert_scratchpad_to_think (line 16) | def convert_scratchpad_to_think(content: str) -> str:
  function has_incomplete_scratchpad (line 23) | def has_incomplete_scratchpad(content: str) -> bool:
  function save_trajectory (line 30) | def save_trajectory(trajectory: List[Dict[str, Any]], model: str,

FILE: agent/usage_pricing.py
  class CanonicalUsage (line 28) | class CanonicalUsage:
    method prompt_tokens (line 38) | def prompt_tokens(self) -> int:
    method total_tokens (line 42) | def total_tokens(self) -> int:
  class BillingRoute (line 47) | class BillingRoute:
  class PricingEntry (line 55) | class PricingEntry:
  class CostResult (line 68) | class CostResult:
  function _to_decimal (line 290) | def _to_decimal(value: Any) -> Optional[Decimal]:
  function _to_int (line 299) | def _to_int(value: Any) -> int:
  function resolve_billing_route (line 306) | def resolve_billing_route(
  function _lookup_official_docs_pricing (line 333) | def _lookup_official_docs_pricing(route: BillingRoute) -> Optional[Prici...
  function _openrouter_pricing_entry (line 337) | def _openrouter_pricing_entry(route: BillingRoute) -> Optional[PricingEn...
  function _pricing_entry_from_metadata (line 346) | def _pricing_entry_from_metadata(
  function get_pricing_entry (line 390) | def get_pricing_entry(
  function normalize_usage (line 420) | def normalize_usage(
  function estimate_usage_cost (line 481) | def estimate_usage_cost(
  function has_known_pricing (line 560) | def has_known_pricing(
  function get_pricing (line 578) | def get_pricing(
  function estimate_cost_usd (line 598) | def estimate_cost_usd(
  function format_duration_compact (line 622) | def format_duration_compact(seconds: float) -> str:
  function format_token_count_compact (line 636) | def format_token_count_compact(value: int) -> str:

FILE: batch_runner.py
  function _normalize_tool_stats (line 58) | def _normalize_tool_stats(tool_stats: Dict[str, Dict[str, int]]) -> Dict...
  function _normalize_tool_error_counts (line 88) | def _normalize_tool_error_counts(tool_error_counts: Dict[str, int]) -> D...
  function _extract_tool_stats (line 112) | def _extract_tool_stats(messages: List[Dict[str, Any]]) -> Dict[str, Dic...
  function _extract_reasoning_stats (line 194) | def _extract_reasoning_stats(messages: List[Dict[str, Any]]) -> Dict[str...
  function _process_single_prompt (line 230) | def _process_single_prompt(
  function _process_batch_worker (line 385) | def _process_batch_worker(args: Tuple) -> Dict[str, Any]:
  class BatchRunner (line 511) | class BatchRunner:
    method __init__ (line 516) | def __init__(
    method _load_dataset (line 621) | def _load_dataset(self) -> List[Dict[str, Any]]:
    method _create_batches (line 653) | def _create_batches(self) -> List[List[Tuple[int, Dict[str, Any]]]]:
    method _load_checkpoint (line 667) | def _load_checkpoint(self) -> Dict[str, Any]:
    method _save_checkpoint (line 694) | def _save_checkpoint(self, checkpoint_data: Dict[str, Any], lock: Opti...
    method _scan_completed_prompts_by_content (line 711) | def _scan_completed_prompts_by_content(self) -> set:
    method _filter_dataset_by_completed (line 755) | def _filter_dataset_by_completed(self, completed_prompts: set) -> Tupl...
    method run (line 789) | def run(self, resume: bool = False):
  function main (line 1110) | def main(

FILE: cli.py
  function _load_prefill_messages (line 88) | def _load_prefill_messages(file_path: str) -> List[Dict[str, Any]]:
  function _parse_reasoning_config (line 117) | def _parse_reasoning_config(effort: str) -> dict | None:
  function load_cli_config (line 135) | def load_cli_config() -> Dict[str, Any]:
  function _run_cleanup (line 483) | def _run_cleanup():
  function _git_repo_root (line 512) | def _git_repo_root() -> Optional[str]:
  function _path_is_within_root (line 527) | def _path_is_within_root(path: Path, root: Path) -> bool:
  function _setup_worktree (line 536) | def _setup_worktree(repo_root: str = None) -> Optional[Dict[str, str]]:
  function _cleanup_worktree (line 635) | def _cleanup_worktree(info: Dict[str, str] = None) -> None:
  function _prune_stale_worktrees (line 692) | def _prune_stale_worktrees(repo_root: str, max_age_hours: int = 24) -> N...
  function _accent_hex (line 768) | def _accent_hex() -> str:
  function _rich_text_from_ansi (line 777) | def _rich_text_from_ansi(text: str) -> _RichText:
  function _cprint (line 786) | def _cprint(text: str):
  class ChatConsole (line 796) | class ChatConsole:
    method __init__ (line 805) | def __init__(self):
    method print (line 815) | def print(self, *args, **kwargs):
  function _build_compact_banner (line 860) | def _build_compact_banner() -> str:
  function _parse_skills_argument (line 896) | def _parse_skills_argument(skills: str | list[str] | tuple[str, ...] | N...
  function save_config_value (line 920) | def save_config_value(key_path: str, value: any) -> bool:
  class HermesCLI (line 982) | class HermesCLI:
    method __init__ (line 990) | def __init__(
    method _invalidate (line 1237) | def _invalidate(self, min_interval: float = 0.25) -> None:
    method _status_bar_context_style (line 1245) | def _status_bar_context_style(self, percent_used: Optional[int]) -> str:
    method _build_context_bar (line 1256) | def _build_context_bar(self, percent_used: Optional[int], width: int =...
    method _get_status_bar_snapshot (line 1261) | def _get_status_bar_snapshot(self) -> Dict[str, Any]:
    method _build_status_bar_text (line 1313) | def _build_status_bar_text(self, width: Optional[int] = None) -> str:
    method _get_status_bar_fragments (line 1341) | def _get_status_bar_fragments(self):
    method _normalize_model_for_provider (line 1401) | def _normalize_model_for_provider(self, resolved_provider: str) -> bool:
    method _on_thinking (line 1463) | def _on_thinking(self, text: str) -> None:
    method _stream_reasoning_delta (line 1470) | def _stream_reasoning_delta(self, text: str) -> None:
    method _close_reasoning_box (line 1495) | def _close_reasoning_box(self) -> None:
    method _stream_delta (line 1507) | def _stream_delta(self, text) -> None:
    method _emit_stream_text (line 1604) | def _emit_stream_text(self, text: str) -> None:
    method _flush_stream (line 1648) | def _flush_stream(self) -> None:
    method _reset_stream_state (line 1663) | def _reset_stream_state(self) -> None:
    method _slow_command_status (line 1674) | def _slow_command_status(self, command: str) -> str:
    method _command_spinner_frame (line 1693) | def _command_spinner_frame(self) -> str:
    method _busy_command (line 1701) | def _busy_command(self, status: str):
    method _ensure_runtime_credentials (line 1714) | def _ensure_runtime_credentials(self) -> bool:
    method _resolve_turn_agent_config (line 1777) | def _resolve_turn_agent_config(self, user_message: str) -> dict:
    method _init_agent (line 1795) | def _init_agent(self, *, model_override: str = None, runtime_override:...
    method show_banner (line 1925) | def show_banner(self):
    method _preload_resumed_session (line 1972) | def _preload_resumed_session(self) -> bool:
    method _display_resumed_history (line 2030) | def _display_resumed_history(self):
    method _try_attach_clipboard_image (line 2181) | def _try_attach_clipboard_image(self) -> bool:
    method _handle_rollback_command (line 2200) | def _handle_rollback_command(self, command: str):
    method _resolve_checkpoint_ref (line 2294) | def _resolve_checkpoint_ref(self, ref: str, checkpoints: list) -> str ...
    method _handle_stop_command (line 2307) | def _handle_stop_command(self):
    method _handle_paste_command (line 2327) | def _handle_paste_command(self):
    method _preprocess_images_with_vision (line 2344) | def _preprocess_images_with_vision(self, text: str, images: list) -> str:
    method _show_tool_availability_warnings (line 2407) | def _show_tool_availability_warnings(self):
    method _show_status (line 2429) | def _show_status(self):
    method show_help (line 2461) | def show_help(self):
    method show_tools (line 2494) | def show_tools(self):
    method _handle_tools_command (line 2536) | def _handle_tools_command(self, cmd: str):
    method show_toolsets (line 2598) | def show_toolsets(self):
    method show_config (line 2629) | def show_config(self):
    method show_history (line 2679) | def show_history(self):
    method new_session (line 2745) | def new_session(self, silent=False):
    method reset_conversation (line 2800) | def reset_conversation(self):
    method save_conversation (line 2804) | def save_conversation(self):
    method retry_last (line 2824) | def retry_last(self):
    method undo_last (line 2853) | def undo_last(self):
    method _show_model_and_providers (line 2885) | def _show_model_and_providers(self):
    method _handle_prompt_command (line 2959) | def _handle_prompt_command(self, cmd: str):
    method _resolve_personality_prompt (line 3015) | def _resolve_personality_prompt(value) -> str:
    method _handle_personality_command (line 3026) | def _handle_personality_command(self, cmd: str):
    method _handle_cron_command (line 3071) | def _handle_cron_command(self, cmd: str):
    method _handle_skills_command (line 3316) | def _handle_skills_command(self, cmd: str):
    method _show_gateway_status (line 3321) | def _show_gateway_status(self):
    method process_command (line 3378) | def process_command(self, command: str) -> bool:
    method _handle_plan_command (line 3812) | def _handle_plan_command(self, cmd: str):
    method _handle_background_command (line 3838) | def _handle_background_command(self, cmd: str):
    method _try_launch_chrome_debug (line 3953) | def _try_launch_chrome_debug(port: int, system: str) -> bool:
    method _handle_browser_command (line 3995) | def _handle_browser_command(self, cmd: str):
    method _handle_skin_command (line 4156) | def _handle_skin_command(self, cmd: str):
    method _toggle_verbose (line 4195) | def _toggle_verbose(self):
    method _handle_reasoning_command (line 4222) | def _handle_reasoning_command(self, cmd: str):
    method _on_reasoning (line 4283) | def _on_reasoning(self, reasoning_text: str):
    method _manual_compress (line 4297) | def _manual_compress(self):
    method _show_usage (line 4338) | def _show_usage(self):
    method _show_insights (line 4416) | def _show_insights(self, command: str = "/insights"):
    method _check_config_mcp_changes (line 4449) | def _check_config_mcp_changes(self) -> None:
    method _reload_mcp (line 4499) | def _reload_mcp(self):
    method _on_tool_progress (line 4589) | def _on_tool_progress(self, function_name: str, preview: str, function...
    method _voice_start_recording (line 4623) | def _voice_start_recording(self):
    method _voice_stop_and_transcribe (line 4702) | def _voice_stop_and_transcribe(self):
    method _voice_speak_response (line 4798) | def _voice_speak_response(self, text: str):
    method _handle_voice_command (line 4852) | def _handle_voice_command(self, command: str):
    method _enable_voice_mode (line 4875) | def _enable_voice_mode(self):
    method _disable_voice_mode (line 4931) | def _disable_voice_mode(self):
    method _toggle_voice_tts (line 4963) | def _toggle_voice_tts(self):
    method _show_voice_status (line 4980) | def _show_voice_status(self):
    method _clarify_callback (line 4998) | def _clarify_callback(self, question, choices):
    method _sudo_password_callback (line 5065) | def _sudo_password_callback(self) -> str:
    method _approval_callback (line 5108) | def _approval_callback(self, command: str, description: str,
    method _approval_choices (line 5163) | def _approval_choices(self, command: str, *, allow_permanent: bool = T...
    method _handle_approval_selection (line 5170) | def _handle_approval_selection(self) -> None:
    method _get_approval_display_fragments (line 5194) | def _get_approval_display_fragments(self):
    method _secret_capture_callback (line 5273) | def _secret_capture_callback(self, var_name: str, prompt: str, metadat...
    method _submit_secret_response (line 5276) | def _submit_secret_response(self, value: str) -> None:
    method _cancel_secret_capture (line 5284) | def _cancel_secret_capture(self) -> None:
    method _clear_secret_input_buffer (line 5287) | def _clear_secret_input_buffer(self) -> None:
    method _clear_current_input (line 5294) | def _clear_current_input(self) -> None:
    method chat (line 5302) | def chat(self, message, images: list = None) -> Optional[str]:
    method _print_exit_summary (line 5658) | def _print_exit_summary(self):
    method _get_tui_prompt_symbols (line 5689) | def _get_tui_prompt_symbols(self) -> tuple[str, str]:
    method _audio_level_bar (line 5716) | def _audio_level_bar(self) -> str:
    method _get_tui_prompt_fragments (line 5728) | def _get_tui_prompt_fragments(self):
    method _get_tui_prompt_text (line 5754) | def _get_tui_prompt_text(self) -> str:
    method _build_tui_style_dict (line 5758) | def _build_tui_style_dict(self) -> dict[str, str]:
    method _apply_tui_skin_style (line 5768) | def _apply_tui_skin_style(self) -> bool:
    method run (line 5776) | def run(self):
  function main (line 6997) | def main(

FILE: cron/jobs.py
  function _normalize_skill_list (line 40) | def _normalize_skill_list(skill: Optional[str] = None, skills: Optional[...
  function _apply_skill_fields (line 57) | def _apply_skill_fields(job: Dict[str, Any]) -> Dict[str, Any]:
  function _secure_dir (line 66) | def _secure_dir(path: Path):
  function _secure_file (line 74) | def _secure_file(path: Path):
  function ensure_dirs (line 83) | def ensure_dirs():
  function parse_duration (line 95) | def parse_duration(s: str) -> int:
  function parse_schedule (line 116) | def parse_schedule(schedule: str) -> Dict[str, Any]:
  function _ensure_aware (line 205) | def _ensure_aware(dt: datetime) -> datetime:
  function _recoverable_oneshot_run_at (line 224) | def _recoverable_oneshot_run_at(
  function compute_next_run (line 251) | def compute_next_run(schedule: Dict[str, Any], last_run_at: Optional[str...
  function load_jobs (line 287) | def load_jobs() -> List[Dict[str, Any]]:
  function save_jobs (line 301) | def save_jobs(jobs: List[Dict[str, Any]]):
  function create_job (line 320) | def create_job(
  function get_job (line 410) | def get_job(job_id: str) -> Optional[Dict[str, Any]]:
  function list_jobs (line 419) | def list_jobs(include_disabled: bool = False) -> List[Dict[str, Any]]:
  function update_job (line 427) | def update_job(job_id: str, updates: Dict[str, Any]) -> Optional[Dict[st...
  function pause_job (line 460) | def pause_job(job_id: str, reason: Optional[str] = None) -> Optional[Dic...
  function resume_job (line 473) | def resume_job(job_id: str) -> Optional[Dict[str, Any]]:
  function trigger_job (line 492) | def trigger_job(job_id: str) -> Optional[Dict[str, Any]]:
  function remove_job (line 509) | def remove_job(job_id: str) -> bool:
  function mark_job_run (line 520) | def mark_job_run(job_id: str, success: bool, error: Optional[str] = None):
  function get_due_jobs (line 564) | def get_due_jobs() -> List[Dict[str, Any]]:
  function save_job_output (line 641) | def save_job_output(job_id: str, output: str):

FILE: cron/scheduler.py
  function _resolve_origin (line 53) | def _resolve_origin(job: dict) -> Optional[dict]:
  function _resolve_delivery_target (line 65) | def _resolve_delivery_target(job: dict) -> Optional[dict]:
  function _deliver_result (line 109) | def _deliver_result(job: dict, content: str) -> None:
  function _build_job_prompt (line 188) | def _build_job_prompt(job: dict) -> str:
  function run_job (line 248) | def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
  function tick (line 471) | def tick(verbose: bool = True) -> int:

FILE: environments/agent_loop.py
  function resize_tool_pool (line 34) | def resize_tool_pool(max_workers: int):
  class ToolError (line 51) | class ToolError:
  class AgentResult (line 62) | class AgentResult:
  function _extract_reasoning_from_message (line 79) | def _extract_reasoning_from_message(message) -> Optional[str]:
  class HermesAgentLoop (line 117) | class HermesAgentLoop:
    method __init__ (line 131) | def __init__(
    method run (line 167) | async def run(self, messages: List[Dict[str, Any]]) -> AgentResult:
    method _get_managed_state (line 491) | def _get_managed_state(self) -> Optional[Dict[str, Any]]:

FILE: environments/agentic_opd_env.py
  function _build_hint_judge_messages (line 248) | def _build_hint_judge_messages(
  function _parse_hint_result (line 263) | def _parse_hint_result(text: str) -> tuple[int | None, str]:
  function _select_best_hint (line 274) | def _select_best_hint(votes: list[dict]) -> dict | None:
  function _append_hint_to_messages (line 288) | def _append_hint_to_messages(messages: list[dict], hint: str) -> list[di...
  class AgenticOPDConfig (line 318) | class AgenticOPDConfig(HermesAgentEnvConfig):
  class AgenticOPDEnv (line 379) | class AgenticOPDEnv(HermesAgentBaseEnv):
    method config_init (line 398) | def config_init(cls) -> Tuple[AgenticOPDConfig, List[APIServerConfig]]:
    method __init__ (line 437) | def __init__(self, *args, **kwargs):
    method setup (line 455) | async def setup(self) -> None:
    method get_next_item (line 515) | async def get_next_item(self) -> dict:
    method format_prompt (line 527) | def format_prompt(self, item: dict) -> str:
    method compute_reward (line 551) | async def compute_reward(
    method collect_trajectories (line 643) | async def collect_trajectories(
    method _apply_opd_pipeline (line 671) | async def _apply_opd_pipeline(self, group: ScoredDataGroup) -> None:
    method _opd_for_sequence (line 727) | async def _opd_for_sequence(
    method _extract_turn_pairs (line 862) | def _extract_turn_pairs(
    method _extract_hint (line 929) | async def _extract_hint(
    method _find_token_span (line 981) | def _find_token_span(
    method evaluate (line 1008) | async def evaluate(self, *args, **kwargs) -> None:
    method wandb_log (line 1158) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None) -> None:

FILE: environments/benchmarks/tblite/tblite_env.py
  class TBLiteEvalConfig (line 44) | class TBLiteEvalConfig(TerminalBench2EvalConfig):
  class TBLiteEvalEnv (line 63) | class TBLiteEvalEnv(TerminalBench2EvalEnv):
    method config_init (line 75) | def config_init(cls) -> Tuple[TBLiteEvalConfig, List[APIServerConfig]]:

FILE: environments/benchmarks/terminalbench_2/terminalbench2_env.py
  class TerminalBench2EvalConfig (line 76) | class TerminalBench2EvalConfig(HermesAgentEnvConfig):
  function _extract_base64_tar (line 151) | def _extract_base64_tar(b64_data: str, target_dir: Path):
  class TerminalBench2EvalEnv (line 165) | class TerminalBench2EvalEnv(HermesAgentBaseEnv):
    method config_init (line 192) | def config_init(cls) -> Tuple[TerminalBench2EvalConfig, List[APIServer...
    method setup (line 259) | async def setup(self):
    method _save_result (line 320) | def _save_result(self, result: Dict[str, Any]):
    method get_next_item (line 335) | async def get_next_item(self):
    method format_prompt (line 341) | def format_prompt(self, item: Dict[str, Any]) -> str:
    method compute_reward (line 345) | async def compute_reward(self, item, result, ctx) -> float:
    method collect_trajectories (line 349) | async def collect_trajectories(self, item):
    method score (line 353) | async def score(self, rollout_group_data):
    method _resolve_task_image (line 361) | def _resolve_task_image(
    method rollout_and_score_eval (line 411) | async def rollout_and_score_eval(self, eval_item: Dict[str, Any]) -> D...

FILE: environments/benchmarks/yc_bench/yc_bench_env.py
  class YCBenchEvalConfig (line 179) | class YCBenchEvalConfig(HermesAgentEnvConfig):
  function _read_final_score (line 232) | def _read_final_score(db_path: str) -> Dict[str, Any]:
  function _compute_composite_score (line 294) | def _compute_composite_score(
  class YCBenchEvalEnv (line 329) | class YCBenchEvalEnv(HermesAgentBaseEnv):
    method config_init (line 348) | def config_init(cls) -> Tuple[YCBenchEvalConfig, List[APIServerConfig]]:
    method setup (line 391) | async def setup(self):
    method _save_result (line 433) | def _save_result(self, result: Dict[str, Any]):
    method get_next_item (line 447) | async def get_next_item(self):
    method format_prompt (line 452) | def format_prompt(self, item: Dict[str, Any]) -> str:
    method compute_reward (line 469) | async def compute_reward(self, item, result, ctx) -> float:
    method collect_trajectories (line 472) | async def collect_trajectories(self, item):
    method score (line 475) | async def score(self, rollout_group_data):
    method rollout_and_score_eval (line 482) | async def rollout_and_score_eval(self, eval_item: Dict[str, Any]) -> D...
    method _run_with_timeout (line 625) | async def _run_with_timeout(self, item: Dict[str, Any]) -> Dict:
    method evaluate (line 654) | async def evaluate(self, *args, **kwargs) -> None:
    method wandb_log (line 836) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None):

FILE: environments/hermes_base_env.py
  class HermesAgentEnvConfig (line 73) | class HermesAgentEnvConfig(BaseEnvConfig):
  class HermesAgentBaseEnv (line 180) | class HermesAgentBaseEnv(BaseEnv):
    method __init__ (line 205) | def __init__(
    method _resolve_tools_for_group (line 248) | def _resolve_tools_for_group(self) -> Tuple[List[Dict[str, Any]], Set[...
    method _use_managed_server (line 287) | def _use_managed_server(self) -> bool:
    method collect_trajectories (line 309) | async def collect_trajectories(
    method _format_trajectory_for_display (line 335) | def _format_trajectory_for_display(messages: List[Dict[str, Any]]) -> ...
    method add_rollouts_for_wandb (line 386) | async def add_rollouts_for_wandb(
    method wandb_log (line 421) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None):
    method collect_trajectory (line 448) | async def collect_trajectory(
    method setup (line 607) | async def setup(self):
    method get_next_item (line 618) | async def get_next_item(self) -> Item:
    method format_prompt (line 628) | def format_prompt(self, item: Item) -> str:
    method compute_reward (line 641) | async def compute_reward(
    method evaluate (line 663) | async def evaluate(self, *args, **kwargs):

FILE: environments/hermes_swe_env/hermes_swe_env.py
  class HermesSweEnvConfig (line 56) | class HermesSweEnvConfig(HermesAgentEnvConfig):
  class HermesSweEnv (line 62) | class HermesSweEnv(HermesAgentBaseEnv):
    method config_init (line 77) | def config_init(cls) -> Tuple[HermesSweEnvConfig, List[APIServerConfig]]:
    method setup (line 124) | async def setup(self):
    method get_next_item (line 136) | async def get_next_item(self) -> Dict[str, Any]:
    method format_prompt (line 144) | def format_prompt(self, item: Dict[str, Any]) -> str:
    method compute_reward (line 160) | async def compute_reward(
    method evaluate (line 195) | async def evaluate(self, *args, **kwargs):
    method wandb_log (line 211) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None):

FILE: environments/patches.py
  class _AsyncWorker (line 38) | class _AsyncWorker:
    method __init__ (line 47) | def __init__(self):
    method start (line 52) | def start(self):
    method _run_loop (line 58) | def _run_loop(self):
    method run_coroutine (line 65) | def run_coroutine(self, coro, timeout=600):
    method stop (line 77) | def stop(self):
  function _patch_swerex_modal (line 85) | def _patch_swerex_modal():
  function apply_patches (line 190) | def apply_patches():

FILE: environments/terminal_test_env/terminal_test_env.py
  class TerminalTestEnvConfig (line 85) | class TerminalTestEnvConfig(HermesAgentEnvConfig):
  class TerminalTestEnv (line 91) | class TerminalTestEnv(HermesAgentBaseEnv):
    method config_init (line 110) | def config_init(cls) -> Tuple[TerminalTestEnvConfig, List[APIServerCon...
    method setup (line 159) | async def setup(self):
    method get_next_item (line 167) | async def get_next_item(self) -> Dict[str, str]:
    method format_prompt (line 173) | def format_prompt(self, item: Dict[str, str]) -> str:
    method compute_reward (line 177) | async def compute_reward(
    method evaluate (line 212) | async def evaluate(self, *args, **kwargs):
    method wandb_log (line 272) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None):

FILE: environments/tool_call_parsers/__init__.py
  class ToolCallParser (line 35) | class ToolCallParser(ABC):
    method parse (line 44) | def parse(self, text: str) -> ParseResult:
  function register_parser (line 65) | def register_parser(name: str):
  function get_parser (line 82) | def get_parser(name: str) -> ToolCallParser:
  function list_parsers (line 103) | def list_parsers() -> List[str]:

FILE: environments/tool_call_parsers/deepseek_v3_1_parser.py
  class DeepSeekV31ToolCallParser (line 26) | class DeepSeekV31ToolCallParser(ToolCallParser):
    method parse (line 42) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/deepseek_v3_parser.py
  class DeepSeekV3ToolCallParser (line 31) | class DeepSeekV3ToolCallParser(ToolCallParser):
    method parse (line 49) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/glm45_parser.py
  function _deserialize_value (line 29) | def _deserialize_value(value: str) -> Any:
  class Glm45ToolCallParser (line 48) | class Glm45ToolCallParser(ToolCallParser):
    method parse (line 64) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/glm47_parser.py
  class Glm47ToolCallParser (line 18) | class Glm47ToolCallParser(Glm45ToolCallParser):
    method __init__ (line 24) | def __init__(self):

FILE: environments/tool_call_parsers/hermes_parser.py
  class HermesToolCallParser (line 22) | class HermesToolCallParser(ToolCallParser):
    method parse (line 35) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/kimi_k2_parser.py
  class KimiK2ToolCallParser (line 27) | class KimiK2ToolCallParser(ToolCallParser):
    method parse (line 50) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/llama_parser.py
  class LlamaToolCallParser (line 26) | class LlamaToolCallParser(ToolCallParser):
    method parse (line 40) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/longcat_parser.py
  class LongcatToolCallParser (line 22) | class LongcatToolCallParser(ToolCallParser):
    method parse (line 33) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/mistral_parser.py
  function _generate_mistral_id (line 25) | def _generate_mistral_id() -> str:
  class MistralToolCallParser (line 34) | class MistralToolCallParser(ToolCallParser):
    method parse (line 48) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/qwen3_coder_parser.py
  function _try_convert_value (line 32) | def _try_convert_value(value: str) -> Any:
  class Qwen3CoderToolCallParser (line 60) | class Qwen3CoderToolCallParser(ToolCallParser):
    method _parse_function_call (line 86) | def _parse_function_call(self, function_str: str) -> Optional[ChatComp...
    method parse (line 122) | def parse(self, text: str) -> ParseResult:

FILE: environments/tool_call_parsers/qwen_parser.py
  class QwenToolCallParser (line 13) | class QwenToolCallParser(HermesToolCallParser):

FILE: environments/tool_context.py
  function _run_tool_in_thread (line 44) | def _run_tool_in_thread(tool_name: str, arguments: Dict[str, Any], task_...
  class ToolContext (line 67) | class ToolContext:
    method __init__ (line 76) | def __init__(self, task_id: str):
    method terminal (line 83) | def terminal(self, command: str, timeout: int = 180) -> Dict[str, Any]:
    method read_file (line 113) | def read_file(self, path: str) -> Dict[str, Any]:
    method write_file (line 131) | def write_file(self, path: str, content: str) -> Dict[str, Any]:
    method upload_file (line 153) | def upload_file(self, local_path: str, remote_path: str) -> Dict[str, ...
    method upload_dir (line 207) | def upload_dir(self, local_dir: str, remote_dir: str) -> List[Dict[str...
    method download_file (line 234) | def download_file(self, remote_path: str, local_path: str) -> Dict[str...
    method download_dir (line 280) | def download_dir(self, remote_dir: str, local_dir: str) -> List[Dict[s...
    method search (line 324) | def search(self, query: str, path: str = ".") -> Dict[str, Any]:
    method web_search (line 347) | def web_search(self, query: str) -> Dict[str, Any]:
    method web_extract (line 363) | def web_extract(self, urls: List[str]) -> Dict[str, Any]:
    method browser_navigate (line 383) | def browser_navigate(self, url: str) -> Dict[str, Any]:
    method browser_snapshot (line 401) | def browser_snapshot(self) -> Dict[str, Any]:
    method call_tool (line 420) | def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> str:
    method cleanup (line 440) | def cleanup(self):

FILE: environments/web_research_env.py
  class WebResearchEnvConfig (line 149) | class WebResearchEnvConfig(HermesAgentEnvConfig):
  class WebResearchEnv (line 201) | class WebResearchEnv(HermesAgentBaseEnv):
    method config_init (line 223) | def config_init(cls) -> Tuple[WebResearchEnvConfig, List[APIServerConf...
    method __init__ (line 253) | def __init__(self, *args, **kwargs):
    method setup (line 270) | async def setup(self) -> None:
    method get_next_item (line 315) | async def get_next_item(self) -> dict:
    method format_prompt (line 327) | def format_prompt(self, item: dict) -> str:
    method compute_reward (line 345) | async def compute_reward(
    method evaluate (line 427) | async def evaluate(self, *args, **kwargs) -> None:
    method wandb_log (line 574) | async def wandb_log(self, wandb_metrics: Optional[Dict] = None) -> None:
    method _llm_judge (line 609) | async def _llm_judge(
    method _parse_judge_json (line 655) | def _parse_judge_json(text: str) -> Optional[float]:
    method _heuristic_score (line 672) | def _heuristic_score(expected: str, model_answer: str) -> float:
    method _extract_domains (line 698) | def _extract_domains(text: str) -> set:

FILE: gateway/channel_directory.py
  function _session_entry_id (line 22) | def _session_entry_id(origin: Dict[str, Any]) -> Optional[str]:
  function _session_entry_name (line 32) | def _session_entry_name(origin: Dict[str, Any]) -> str:
  function build_channel_directory (line 46) | def build_channel_directory(adapters: Dict[Any, Any]) -> Dict[str, Any]:
  function _build_discord (line 85) | def _build_discord(adapter) -> List[Dict[str, str]]:
  function _build_slack (line 113) | def _build_slack(adapter) -> List[Dict[str, str]]:
  function _build_from_sessions (line 132) | def _build_from_sessions(platform_name: str) -> List[Dict[str, str]]:
  function load_directory (line 168) | def load_directory() -> Dict[str, Any]:
  function resolve_channel_name (line 179) | def resolve_channel_name(platform_name: str, name: str) -> Optional[str]:
  function format_directory_for_display (line 216) | def format_directory_for_display() -> str:

FILE: gateway/config.py
  function _coerce_bool (line 24) | def _coerce_bool(value: Any, default: bool = True) -> bool:
  function _normalize_unauthorized_dm_behavior (line 35) | def _normalize_unauthorized_dm_behavior(value: Any, default: str = "pair...
  class Platform (line 44) | class Platform(Enum):
  class HomeChannel (line 63) | class HomeChannel:
    method to_dict (line 74) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 82) | def from_dict(cls, data: Dict[str, Any]) -> "HomeChannel":
  class SessionResetPolicy (line 91) | class SessionResetPolicy:
    method to_dict (line 105) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 113) | def from_dict(cls, data: Dict[str, Any]) -> "SessionResetPolicy":
  class PlatformConfig (line 126) | class PlatformConfig:
    method to_dict (line 136) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 150) | def from_dict(cls, data: Dict[str, Any]) -> "PlatformConfig":
  class StreamingConfig (line 165) | class StreamingConfig:
    method to_dict (line 173) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 183) | def from_dict(cls, data: Dict[str, Any]) -> "StreamingConfig":
  class GatewayConfig (line 196) | class GatewayConfig:
    method get_connected_platforms (line 234) | def get_connected_platforms(self) -> List[Platform]:
    method get_home_channel (line 263) | def get_home_channel(self, platform: Platform) -> Optional[HomeChannel]:
    method get_reset_policy (line 270) | def get_reset_policy(
    method to_dict (line 290) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 313) | def from_dict(cls, data: Dict[str, Any]) -> "GatewayConfig":
    method get_unauthorized_dm_behavior (line 371) | def get_unauthorized_dm_behavior(self, platform: Optional[Platform] = ...
  function load_gateway_config (line 383) | def load_gateway_config() -> GatewayConfig:
  function _apply_env_overrides (line 549) | def _apply_env_overrides(config: GatewayConfig) -> None:

FILE: gateway/delivery.py
  class DeliveryTarget (line 30) | class DeliveryTarget:
    method parse (line 47) | def parse(cls, target: str, origin: Optional[SessionSource] = None) ->...
    method to_string (line 92) | def to_string(self) -> str:
  class DeliveryRouter (line 103) | class DeliveryRouter:
    method __init__ (line 111) | def __init__(self, config: GatewayConfig, adapters: Dict[Platform, Any...
    method resolve_targets (line 123) | def resolve_targets(
    method deliver (line 170) | async def deliver(
    method _deliver_local (line 212) | def _deliver_local(
    method _save_full_output (line 258) | def _save_full_output(self, content: str, job_id: str) -> Path:
    method _deliver_to_platform (line 267) | async def _deliver_to_platform(
  function parse_deliver_spec (line 298) | def parse_deliver_spec(
  function build_delivery_context_for_tool (line 313) | def build_delivery_context_for_tool(

FILE: gateway/hooks.py
  class HookRegistry (line 36) | class HookRegistry:
    method __init__ (line 46) | def __init__(self):
    method loaded_hooks (line 52) | def loaded_hooks(self) -> List[dict]:
    method discover_and_load (line 56) | def discover_and_load(self) -> None:
    method emit (line 121) | async def emit(self, event_type: str, context: Optional[Dict[str, Any]...

FILE: gateway/mirror.py
  function mirror_to_session (line 26) | def mirror_to_session(
  function _find_session_id (line 67) | def _find_session_id(platform: str, chat_id: str, thread_id: Optional[st...
  function _append_to_jsonl (line 108) | def _append_to_jsonl(session_id: str, message: dict) -> None:
  function _append_to_sqlite (line 118) | def _append_to_sqlite(session_id: str, message: dict) -> None:

FILE: gateway/pairing.py
  function _secure_write (line 47) | def _secure_write(path: Path, data: str) -> None:
  class PairingStore (line 57) | class PairingStore:
    method __init__ (line 67) | def __init__(self):
    method _pending_path (line 70) | def _pending_path(self, platform: str) -> Path:
    method _approved_path (line 73) | def _approved_path(self, platform: str) -> Path:
    method _rate_limit_path (line 76) | def _rate_limit_path(self) -> Path:
    method _load_json (line 79) | def _load_json(self, path: Path) -> dict:
    method _save_json (line 87) | def _save_json(self, path: Path, data: dict) -> None:
    method is_approved (line 92) | def is_approved(self, platform: str, user_id: str) -> bool:
    method list_approved (line 97) | def list_approved(self, platform: str = None) -> list:
    method _approve_user (line 107) | def _approve_user(self, platform: str, user_id: str, user_name: str = ...
    method revoke (line 116) | def revoke(self, platform: str, user_id: str) -> bool:
    method generate_code (line 128) | def generate_code(
    method approve_code (line 170) | def approve_code(self, platform: str, code: str) -> Optional[dict]:
    method list_pending (line 195) | def list_pending(self, platform: str = None) -> list:
    method clear_pending (line 213) | def clear_pending(self, platform: str = None) -> int:
    method _is_rate_limited (line 225) | def _is_rate_limited(self, platform: str, user_id: str) -> bool:
    method _record_rate_limit (line 232) | def _record_rate_limit(self, platform: str, user_id: str) -> None:
    method _is_locked_out (line 239) | def _is_locked_out(self, platform: str) -> bool:
    method _record_failed_attempt (line 246) | def _record_failed_attempt(self, platform: str) -> None:
    method _cleanup_expired (line 262) | def _cleanup_expired(self, platform: str) -> None:
    method _all_platforms (line 276) | def _all_platforms(self, suffix: str) -> list:

FILE: gateway/platforms/api_server.py
  function check_api_server_requirements (line 50) | def check_api_server_requirements() -> bool:
  class ResponseStore (line 55) | class ResponseStore:
    method __init__ (line 64) | def __init__(self, max_size: int = MAX_STORED_RESPONSES):
    method get (line 68) | def get(self, response_id: str) -> Optional[Dict[str, Any]]:
    method put (line 75) | def put(self, response_id: str, data: Dict[str, Any]) -> None:
    method delete (line 83) | def delete(self, response_id: str) -> bool:
    method __len__ (line 90) | def __len__(self) -> int:
  function cors_middleware (line 107) | async def cors_middleware(request, handler):
  class APIServerAdapter (line 118) | class APIServerAdapter(BasePlatformAdapter):
    method __init__ (line 126) | def __init__(self, config: PlatformConfig):
    method _check_auth (line 143) | def _check_auth(self, request: "web.Request") -> Optional["web.Respons...
    method _create_agent (line 168) | def _create_agent(
    method _handle_health (line 205) | async def _handle_health(self, request: "web.Request") -> "web.Response":
    method _handle_models (line 209) | async def _handle_models(self, request: "web.Request") -> "web.Response":
    method _handle_chat_completions (line 230) | async def _handle_chat_completions(self, request: "web.Request") -> "w...
    method _write_sse_chat_completion (line 351) | async def _write_sse_chat_completion(
    method _handle_responses (line 430) | async def _handle_responses(self, request: "web.Request") -> "web.Resp...
    method _handle_get_response (line 597) | async def _handle_get_response(self, request: "web.Request") -> "web.R...
    method _handle_delete_response (line 613) | async def _handle_delete_response(self, request: "web.Request") -> "we...
    method _extract_output_items (line 638) | def _extract_output_items(result: Dict[str, Any]) -> List[Dict[str, An...
    method _run_agent (line 689) | async def _run_agent(
    method connect (line 728) | async def connect(self) -> bool:
    method disconnect (line 759) | async def disconnect(self) -> None:
    method send (line 771) | async def send(
    method get_chat_info (line 783) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:

FILE: gateway/platforms/base.py
  function get_image_cache_dir (line 50) | def get_image_cache_dir() -> Path:
  function cache_image_from_bytes (line 56) | def cache_image_from_bytes(data: bytes, ext: str = ".jpg") -> str:
  function cache_image_from_url (line 74) | async def cache_image_from_url(url: str, ext: str = ".jpg") -> str:
  function cleanup_image_cache (line 101) | def cleanup_image_cache(max_age_hours: int = 24) -> int:
  function get_audio_cache_dir (line 132) | def get_audio_cache_dir() -> Path:
  function cache_audio_from_bytes (line 138) | def cache_audio_from_bytes(data: bytes, ext: str = ".ogg") -> str:
  function cache_audio_from_url (line 156) | async def cache_audio_from_url(url: str, ext: str = ".ogg") -> str:
  function get_document_cache_dir (line 200) | def get_document_cache_dir() -> Path:
  function cache_document_from_bytes (line 206) | def cache_document_from_bytes(data: bytes, filename: str) -> str:
  function cleanup_document_cache (line 238) | def cleanup_document_cache(max_age_hours: int = 24) -> int:
  class MessageType (line 259) | class MessageType(Enum):
  class MessageEvent (line 273) | class MessageEvent:
    method is_command (line 302) | def is_command(self) -> bool:
    method get_command (line 306) | def get_command(self) -> Optional[str]:
    method get_command_args (line 314) | def get_command_args(self) -> str:
  class SendResult (line 323) | class SendResult:
  class BasePlatformAdapter (line 335) | class BasePlatformAdapter(ABC):
    method __init__ (line 346) | def __init__(self, config: PlatformConfig, platform: Platform):
    method has_fatal_error (line 368) | def has_fatal_error(self) -> bool:
    method fatal_error_message (line 372) | def fatal_error_message(self) -> Optional[str]:
    method fatal_error_code (line 376) | def fatal_error_code(self) -> Optional[str]:
    method fatal_error_retryable (line 380) | def fatal_error_retryable(self) -> bool:
    method set_fatal_error_handler (line 383) | def set_fatal_error_handler(self, handler: Callable[["BasePlatformAdap...
    method _mark_connected (line 386) | def _mark_connected(self) -> None:
    method _mark_disconnected (line 397) | def _mark_disconnected(self) -> None:
    method _set_fatal_error (line 407) | def _set_fatal_error(self, code: str, message: str, *, retryable: bool...
    method _notify_fatal_error (line 423) | async def _notify_fatal_error(self) -> None:
    method name (line 432) | def name(self) -> str:
    method is_connected (line 437) | def is_connected(self) -> bool:
    method set_message_handler (line 441) | def set_message_handler(self, handler: MessageHandler) -> None:
    method connect (line 451) | async def connect(self) -> bool:
    method disconnect (line 460) | async def disconnect(self) -> None:
    method send (line 465) | async def send(
    method edit_message (line 486) | async def edit_message(
    method send_typing (line 499) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method send_image (line 508) | async def send_image(
    method send_animation (line 527) | async def send_animation(
    method _is_animation_url (line 545) | def _is_animation_url(url: str) -> bool:
    method extract_images (line 551) | def extract_images(content: str) -> Tuple[List[Tuple[str, str]], str]:
    method send_voice (line 598) | async def send_voice(
    method play_tts (line 618) | async def play_tts(
    method send_video (line 632) | async def send_video(
    method send_document (line 651) | async def send_document(
    method send_image_file (line 671) | async def send_image_file(
    method extract_media (line 692) | def extract_media(content: str) -> Tuple[List[Tuple[str, bool]], str]:
    method extract_local_files (line 734) | def extract_local_files(content: str) -> Tuple[List[str], str]:
    method _keep_typing (line 801) | async def _keep_typing(self, chat_id: str, interval: float = 2.0, meta...
    method handle_message (line 815) | async def handle_message(self, event: MessageEvent) -> None:
    method _get_human_delay (line 870) | def _get_human_delay() -> float:
    method _process_message_background (line 890) | async def _process_message_background(self, event: MessageEvent, sessi...
    method cancel_background_tasks (line 1129) | async def cancel_background_tasks(self) -> None:
    method has_pending_interrupt (line 1144) | def has_pending_interrupt(self, session_key: str) -> bool:
    method get_pending_message (line 1148) | def get_pending_message(self, session_key: str) -> Optional[MessageEve...
    method build_source (line 1152) | def build_source(
    method get_chat_info (line 1182) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method format_message (line 1192) | def format_message(self, content: str) -> str:
    method truncate_message (line 1204) | def truncate_message(content: str, max_length: int = 4096) -> List[str]:

FILE: gateway/platforms/dingtalk.py
  function check_dingtalk_requirements (line 59) | def check_dingtalk_requirements() -> bool:
  class DingTalkAdapter (line 68) | class DingTalkAdapter(BasePlatformAdapter):
    method __init__ (line 78) | def __init__(self, config: PlatformConfig):
    method connect (line 96) | async def connect(self) -> bool:
    method _run_stream (line 129) | async def _run_stream(self) -> None:
    method disconnect (line 151) | async def disconnect(self) -> None:
    method _on_message (line 175) | async def _on_message(self, message: "ChatbotMessage") -> None:
    method _extract_text (line 233) | def _extract_text(message: "ChatbotMessage") -> str:
    method _is_duplicate (line 252) | def _is_duplicate(self, msg_id: str) -> bool:
    method send (line 266) | async def send(
    method send_typing (line 302) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method get_chat_info (line 306) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
  class _IncomingHandler (line 315) | class _IncomingHandler(ChatbotHandler if DINGTALK_STREAM_AVAILABLE else ...
    method __init__ (line 318) | def __init__(self, adapter: DingTalkAdapter, loop: asyncio.AbstractEve...
    method process (line 324) | def process(self, message: "ChatbotMessage"):

FILE: gateway/platforms/discord.py
  function _clean_discord_id (line 56) | def _clean_discord_id(entry: str) -> str:
  function check_discord_requirements (line 73) | def check_discord_requirements() -> bool:
  class VoiceReceiver (line 78) | class VoiceReceiver:
    method __init__ (line 92) | def __init__(self, voice_client, allowed_user_ids: set = None):
    method start (line 123) | def start(self):
    method stop (line 135) | def stop(self):
    method pause (line 149) | def pause(self):
    method resume (line 152) | def resume(self):
    method map_ssrc (line 159) | def map_ssrc(self, ssrc: int, user_id: int):
    method _install_speaking_hook (line 163) | def _install_speaking_hook(self, conn):
    method _on_packet (line 200) | def _on_packet(self, data: bytes):
    method _infer_user_for_ssrc (line 312) | def _infer_user_for_ssrc(self, ssrc: int) -> int:
    method check_silence (line 338) | def check_silence(self) -> list:
    method pcm_to_wav (line 376) | def pcm_to_wav(pcm_data: bytes, output_path: str,
  class DiscordAdapter (line 404) | class DiscordAdapter(BasePlatformAdapter):
    method __init__ (line 424) | def __init__(self, config: PlatformConfig):
    method connect (line 445) | async def connect(self) -> bool:
    method disconnect (line 594) | async def disconnect(self) -> None:
    method send (line 614) | async def send(
    method edit_message (line 685) | async def edit_message(
    method _send_file_attachment (line 708) | async def _send_file_attachment(
    method play_tts (line 731) | async def play_tts(
    method send_voice (line 749) | async def send_voice(
    method join_voice_channel (line 828) | async def join_voice_channel(self, channel) -> bool:
    method leave_voice_channel (line 861) | async def leave_voice_channel(self, guild_id: int) -> None:
    method play_in_voice_channel (line 882) | async def play_in_voice_channel(self, guild_id: int, audio_path: str) ...
    method get_user_voice_channel (line 925) | async def get_user_voice_channel(self, guild_id: int, user_id: str):
    method _reset_voice_timeout (line 937) | def _reset_voice_timeout(self, guild_id: int) -> None:
    method _voice_timeout_handler (line 946) | async def _voice_timeout_handler(self, guild_id: int) -> None:
    method is_in_voice_channel (line 968) | def is_in_voice_channel(self, guild_id: int) -> bool:
    method get_voice_channel_info (line 973) | def get_voice_channel_info(self, guild_id: int) -> Optional[Dict[str, ...
    method get_voice_channel_context (line 1025) | def get_voice_channel_context(self, guild_id: int) -> str:
    method _voice_listen_loop (line 1050) | async def _voice_listen_loop(self, guild_id: int):
    method _process_voice_input (line 1082) | async def _process_voice_input(self, guild_id: int, user_id: int, pcm_...
    method _is_allowed_user (line 1118) | def _is_allowed_user(self, user_id: str) -> bool:
    method send_image_file (line 1124) | async def send_image_file(
    method send_image (line 1141) | async def send_image(
    method send_video (line 1206) | async def send_video(
    method send_document (line 1223) | async def send_document(
    method send_typing (line 1241) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method get_chat_info (line 1251) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method _resolve_allowed_usernames (line 1290) | async def _resolve_allowed_usernames(self) -> None:
    method format_message (line 1354) | def format_message(self, content: str) -> str:
    method _run_simple_slash (line 1363) | async def _run_simple_slash(
    method _register_slash_commands (line 1379) | def _register_slash_commands(self) -> None:
    method _build_slash_event (line 1500) | def _build_slash_event(self, interaction: discord.Interaction, text: s...
    method _handle_thread_create_slash (line 1534) | async def _handle_thread_create_slash(
    method _dispatch_thread_session (line 1570) | async def _dispatch_thread_session(
    method _thread_parent_channel (line 1601) | def _thread_parent_channel(self, channel: Any) -> Any:
    method _resolve_interaction_channel (line 1605) | async def _resolve_interaction_channel(self, interaction: discord.Inte...
    method _create_thread (line 1623) | async def _create_thread(
    method _auto_create_thread (line 1698) | async def _auto_create_thread(self, message: 'DiscordMessage') -> Opti...
    method send_exec_approval (line 1716) | async def send_exec_approval(
    method _get_parent_channel_id (line 1753) | def _get_parent_channel_id(self, channel: Any) -> Optional[str]:
    method _is_forum_parent (line 1763) | def _is_forum_parent(self, channel: Any) -> bool:
    method _format_thread_chat_name (line 1777) | def _format_thread_chat_name(self, thread: Any) -> str:
    method _thread_state_path (line 1798) | def _thread_state_path() -> Path:
    method _load_participated_threads (line 1804) | def _load_participated_threads(cls) -> set:
    method _save_participated_threads (line 1816) | def _save_participated_threads(self) -> None:
    method _track_thread (line 1830) | def _track_thread(self, thread_id: str) -> None:
    method _handle_message (line 1836) | async def _handle_message(self, message: DiscordMessage) -> None:
  class ExecApprovalView (line 2003) | class ExecApprovalView(discord.ui.View):
    method __init__ (line 2011) | def __init__(self, approval_id: str, allowed_user_ids: set):
    method _check_auth (line 2017) | def _check_auth(self, interaction: discord.Interaction) -> bool:
    method _resolve (line 2023) | async def _resolve(
    method allow_once (line 2064) | async def allow_once(
    method allow_always (line 2070) | async def allow_always(
    method deny (line 2076) | async def deny(
    method on_timeout (line 2081) | async def on_timeout(self):

FILE: gateway/platforms/email.py
  function check_email_requirements (line 55) | def check_email_requirements() -> bool:
  function _decode_header_value (line 66) | def _decode_header_value(raw: str) -> str:
  function _extract_text_body (line 78) | def _extract_text_body(msg: email_lib.message.Message) -> str:
  function _strip_html (line 116) | def _strip_html(html: str) -> str:
  function _extract_email_address (line 130) | def _extract_email_address(raw: str) -> str:
  function _extract_attachments (line 138) | def _extract_attachments(
  class EmailAdapter (line 194) | class EmailAdapter(BasePlatformAdapter):
    method __init__ (line 197) | def __init__(self, config: PlatformConfig):
    method connect (line 224) | async def connect(self) -> bool:
    method disconnect (line 258) | async def disconnect(self) -> None:
    method _poll_loop (line 270) | async def _poll_loop(self) -> None:
    method _check_inbox (line 281) | async def _check_inbox(self) -> None:
    method _fetch_new_messages (line 289) | def _fetch_new_messages(self) -> List[Dict[str, Any]]:
    method _dispatch_message (line 344) | async def _dispatch_message(self, msg_data: Dict[str, Any]) -> None:
    method send (line 399) | async def send(
    method _send_email (line 417) | def _send_email(
    method send_typing (line 455) | async def send_typing(self, chat_id: str, metadata: Optional[Dict[str,...
    method send_image (line 459) | async def send_image(
    method send_document (line 471) | async def send_document(
    method _send_email_with_attachment (line 495) | def _send_email_with_attachment(
    method get_chat_info (line 542) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:

FILE: gateway/platforms/homeassistant.py
  function check_ha_requirements (line 42) | def check_ha_requirements() -> bool:
  class HomeAssistantAdapter (line 51) | class HomeAssistantAdapter(BasePlatformAdapter):
    method __init__ (line 65) | def __init__(self, config: PlatformConfig):
    method _next_id (line 92) | def _next_id(self) -> int:
    method connect (line 101) | async def connect(self) -> bool:
    method _ws_connect (line 138) | async def _ws_connect(self) -> bool:
    method _cleanup_ws (line 183) | async def _cleanup_ws(self) -> None:
    method disconnect (line 192) | async def disconnect(self) -> None:
    method _listen_loop (line 213) | async def _listen_loop(self) -> None:
    method _read_events (line 243) | async def _read_events(self) -> None:
    method _handle_ha_event (line 258) | async def _handle_ha_event(self, event: Dict[str, Any]) -> None:
    method _format_state_change (line 317) | def _format_state_change(
    method send (line 382) | async def send(
    method send_typing (line 436) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method get_chat_info (line 440) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:

FILE: gateway/platforms/matrix.py
  function check_matrix_requirements (line 50) | def check_matrix_requirements() -> bool:
  class MatrixAdapter (line 73) | class MatrixAdapter(BasePlatformAdapter):
    method __init__ (line 76) | def __init__(self, config: PlatformConfig):
    method connect (line 111) | async def connect(self) -> bool:
    method disconnect (line 226) | async def disconnect(self) -> None:
    method send (line 243) | async def send(
    method get_chat_info (line 303) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method send_typing (line 324) | async def send_typing(
    method edit_message (line 334) | async def edit_message(
    method send_image (line 366) | async def send_image(
    method send_image_file (line 399) | async def send_image_file(
    method send_document (line 410) | async def send_document(
    method send_voice (line 422) | async def send_voice(
    method send_video (line 433) | async def send_video(
    method format_message (line 444) | def format_message(self, content: str) -> str:
    method _upload_and_send (line 454) | async def _upload_and_send(
    method _send_local_file (line 510) | async def _send_local_file(
    method _sync_loop (line 537) | async def _sync_loop(self) -> None:
    method _on_room_message (line 554) | async def _on_room_message(self, room: Any, event: Any) -> None:
    method _on_room_message_media (line 643) | async def _on_room_message_media(self, room: Any, event: Any) -> None:
    method _on_invite (line 716) | async def _on_invite(self, room: Any, event: Any) -> None:
    method _refresh_dm_cache (line 753) | async def _refresh_dm_cache(self) -> None:
    method _get_display_name (line 801) | def _get_display_name(self, room: Any, user_id: str) -> str:
    method _mxc_to_http (line 812) | def _mxc_to_http(self, mxc_url: str) -> str:
    method _markdown_to_html (line 823) | def _markdown_to_html(self, text: str) -> str:

FILE: gateway/platforms/mattermost.py
  function check_mattermost_requirements (line 53) | def check_mattermost_requirements() -> bool:
  class MattermostAdapter (line 71) | class MattermostAdapter(BasePlatformAdapter):
    method __init__ (line 74) | def __init__(self, config: PlatformConfig):
    method _headers (line 108) | def _headers(self) -> Dict[str, str]:
    method _api_get (line 114) | async def _api_get(self, path: str) -> Dict[str, Any]:
    method _api_post (line 129) | async def _api_post(
    method _api_put (line 148) | async def _api_put(
    method _upload_file (line 167) | async def _upload_file(
    method connect (line 196) | async def connect(self) -> bool:
    method disconnect (line 228) | async def disconnect(self) -> None:
    method send (line 251) | async def send(
    method get_chat_info (line 282) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method send_typing (line 296) | async def send_typing(
    method edit_message (line 305) | async def edit_message(
    method send_image (line 318) | async def send_image(
    method send_image_file (line 331) | async def send_image_file(
    method send_document (line 344) | async def send_document(
    method send_voice (line 358) | async def send_voice(
    method send_video (line 371) | async def send_video(
    method format_message (line 384) | def format_message(self, content: str) -> str:
    method _send_url_as_file (line 398) | async def _send_url_as_file(
    method _send_local_file (line 438) | async def _send_local_file(
    method _ws_loop (line 480) | async def _ws_loop(self) -> None:
    method _ws_connect_and_listen (line 504) | async def _ws_connect_and_listen(self) -> None:
    method _handle_ws_event (line 543) | async def _handle_ws_event(self, event: Dict[str, Any]) -> None:
    method _prune_seen (line 655) | def _prune_seen(self) -> None:

FILE: gateway/platforms/signal.py
  function _redact_phone (line 62) | def _redact_phone(phone: str) -> str:
  function _parse_comma_list (line 71) | def _parse_comma_list(value: str) -> List[str]:
  function _guess_extension (line 76) | def _guess_extension(data: bytes) -> str:
  function _is_image_ext (line 99) | def _is_image_ext(ext: str) -> bool:
  function _is_audio_ext (line 103) | def _is_audio_ext(ext: str) -> bool:
  function _ext_to_mime (line 116) | def _ext_to_mime(ext: str) -> str:
  function _render_mentions (line 121) | def _render_mentions(text: str, mentions: list) -> str:
  function check_signal_requirements (line 142) | def check_signal_requirements() -> bool:
  class SignalAdapter (line 151) | class SignalAdapter(BasePlatformAdapter):
    method __init__ (line 156) | def __init__(self, config: PlatformConfig):
    method connect (line 195) | async def connect(self) -> bool:
    method disconnect (line 221) | async def disconnect(self) -> None:
    method _sse_listener (line 254) | async def _sse_listener(self) -> None:
    method _health_monitor (line 317) | async def _health_monitor(self) -> None:
    method _force_reconnect (line 343) | def _force_reconnect(self) -> None:
    method _handle_envelope (line 356) | async def _handle_envelope(self, envelope: dict) -> None:
    method _fetch_attachment (line 512) | async def _fetch_attachment(self, attachment_id: str) -> tuple:
    method _rpc (line 539) | async def _rpc(self, method: str, params: dict, rpc_id: str = None) ->...
    method send (line 578) | async def send(
    method _track_sent_timestamp (line 605) | def _track_sent_timestamp(self, rpc_result) -> None:
    method send_typing (line 613) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method send_image (line 626) | async def send_image(
    method send_document (line 672) | async def send_document(
    method _start_typing_indicator (line 707) | async def _start_typing_indicator(self, chat_id: str) -> None:
    method _stop_typing_indicator (line 722) | async def _stop_typing_indicator(self, chat_id: str) -> None:
    method get_chat_info (line 736) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:

FILE: gateway/platforms/slack.py
  function check_slack_requirements (line 48) | def check_slack_requirements() -> bool:
  class SlackAdapter (line 53) | class SlackAdapter(BasePlatformAdapter):
    method __init__ (line 71) | def __init__(self, config: PlatformConfig):
    method connect (line 78) | async def connect(self) -> bool:
    method disconnect (line 134) | async def disconnect(self) -> None:
    method send (line 144) | async def send(
    method edit_message (line 192) | async def edit_message(
    method send_typing (line 218) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method _resolve_thread_ts (line 246) | def _resolve_thread_ts(
    method _upload_file (line 263) | async def _upload_file(
    method format_message (line 289) | def format_message(self, content: str) -> str:
    method _add_reaction (line 373) | async def _add_reaction(
    method _remove_reaction (line 389) | async def _remove_reaction(
    method _resolve_user_name (line 406) | async def _resolve_user_name(self, user_id: str) -> str:
    method send_image_file (line 435) | async def send_image_file(
    method send_image (line 461) | async def send_image(
    method send_voice (line 502) | async def send_voice(
    method send_video (line 525) | async def send_video(
    method send_document (line 563) | async def send_document(
    method get_chat_info (line 604) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method _handle_slack_message (line 628) | async def _handle_slack_message(self, event: dict) -> None:
    method _handle_slash_command (line 786) | async def _handle_slash_command(self, command: dict) -> None:
    method _download_slack_file (line 822) | async def _download_slack_file(self, url: str, ext: str, audio: bool =...
    method _download_slack_file_bytes (line 841) | async def _download_slack_file_bytes(self, url: str) -> bytes:

FILE: gateway/platforms/sms.py
  function _redact_phone (line 45) | def _redact_phone(phone: str) -> str:
  function check_sms_requirements (line 54) | def check_sms_requirements() -> bool:
  class SmsAdapter (line 63) | class SmsAdapter(BasePlatformAdapter):
    method __init__ (line 73) | def __init__(self, config: PlatformConfig):
    method _basic_auth_header (line 84) | def _basic_auth_header(self) -> str:
    method connect (line 94) | async def connect(self) -> bool:
    method disconnect (line 120) | async def disconnect(self) -> None:
    method send (line 130) | async def send(
    method get_chat_info (line 183) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method format_message (line 190) | def format_message(self, content: str) -> str:
    method _handle_webhook (line 207) | async def _handle_webhook(self, request) -> "aiohttp.web.Response":

FILE: gateway/platforms/telegram.py
  class _MockContextTypes (line 43) | class _MockContextTypes:
  function check_telegram_requirements (line 64) | def check_telegram_requirements() -> bool:
  function _escape_mdv2 (line 74) | def _escape_mdv2(text: str) -> str:
  function _strip_mdv2 (line 79) | def _strip_mdv2(text: str) -> str:
  class TelegramAdapter (line 99) | class TelegramAdapter(BasePlatformAdapter):
    method __init__ (line 114) | def __init__(self, config: PlatformConfig):
    method _looks_like_polling_conflict (line 134) | def _looks_like_polling_conflict(error: Exception) -> bool:
    method _handle_polling_conflict (line 142) | async def _handle_polling_conflict(self, error: Exception) -> None:
    method connect (line 159) | async def connect(self) -> bool:
    method disconnect (line 284) | async def disconnect(self) -> None:
    method send (line 323) | async def send(
    method edit_message (line 403) | async def edit_message(
    method send_voice (line 482) | async def send_voice(
    method send_image_file (line 531) | async def send_image_file(
    method send_document (line 565) | async def send_document(
    method send_video (line 597) | async def send_video(
    method send_image (line 625) | async def send_image(
    method send_animation (line 684) | async def send_animation(
    method send_typing (line 716) | async def send_typing(self, chat_id: str, metadata: Optional[Dict[str,...
    method get_chat_info (line 735) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method format_message (line 769) | def format_message(self, content: str) -> str:
    method _handle_text_message (line 887) | async def _handle_text_message(self, update: Update, context: ContextT...
    method _handle_command (line 900) | async def _handle_command(self, update: Update, context: ContextTypes....
    method _handle_location_message (line 908) | async def _handle_location_message(self, update: Update, context: Cont...
    method _text_batch_key (line 947) | def _text_batch_key(self, event: MessageEvent) -> str:
    method _enqueue_text_event (line 955) | def _enqueue_text_event(self, event: MessageEvent) -> None:
    method _flush_text_batch (line 984) | async def _flush_text_batch(self, key: str) -> None:
    method _photo_batch_key (line 1005) | def _photo_batch_key(self, event: MessageEvent, msg: Message) -> str:
    method _flush_photo_batch (line 1017) | async def _flush_photo_batch(self, batch_key: str) -> None:
    method _enqueue_photo_event (line 1031) | def _enqueue_photo_event(self, batch_key: str, event: MessageEvent) ->...
    method _handle_media_message (line 1051) | async def _handle_media_message(self, update: Update, context: Context...
    method _queue_media_group_event (line 1216) | async def _queue_media_group_event(self, media_group_id: str, event: M...
    method _flush_media_group_event (line 1245) | async def _flush_media_group_event(self, media_group_id: str) -> None:
    method _handle_sticker (line 1256) | async def _handle_sticker(self, msg: Message, event: "MessageEvent") -...
    method _build_message_event (line 1323) | def _build_message_event(self, message: Message, msg_type: MessageType...

FILE: gateway/platforms/webhook.py
  function check_webhook_requirements (line 58) | def check_webhook_requirements() -> bool:
  class WebhookAdapter (line 63) | class WebhookAdapter(BasePlatformAdapter):
    method __init__ (line 66) | def __init__(self, config: PlatformConfig):
    method connect (line 98) | async def connect(self) -> bool:
    method disconnect (line 128) | async def disconnect(self) -> None:
    method send (line 135) | async def send(
    method get_chat_info (line 174) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method _handle_health (line 181) | async def _handle_health(self, request: "web.Request") -> "web.Response":
    method _handle_webhook (line 185) | async def _handle_webhook(self, request: "web.Request") -> "web.Respon...
    method _validate_signature (line 382) | def _validate_signature(
    method _render_prompt (line 417) | def _render_prompt(
    method _render_delivery_extra (line 450) | def _render_delivery_extra(
    method _deliver_github_comment (line 466) | async def _deliver_github_comment(
    method _deliver_cross_platform (line 520) | async def _deliver_cross_platform(

FILE: gateway/platforms/whatsapp.py
  function _kill_port_process (line 34) | def _kill_port_process(port: int) -> None:
  function check_whatsapp_requirements (line 82) | def check_whatsapp_requirements() -> bool:
  class WhatsAppAdapter (line 101) | class WhatsAppAdapter(BasePlatformAdapter):
    method __init__ (line 127) | def __init__(self, config: PlatformConfig):
    method connect (line 144) | async def connect(self) -> bool:
    method _close_bridge_log (line 318) | def _close_bridge_log(self) -> None:
    method disconnect (line 327) | async def disconnect(self) -> None:
    method send (line 360) | async def send(
    method edit_message (line 406) | async def edit_message(
    method _send_media_to_bridge (line 435) | async def _send_media_to_bridge(
    method send_image (line 482) | async def send_image(
    method send_image_file (line 496) | async def send_image_file(
    method send_video (line 506) | async def send_video(
    method send_document (line 516) | async def send_document(
    method send_typing (line 530) | async def send_typing(self, chat_id: str, metadata=None) -> None:
    method get_chat_info (line 547) | async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
    method _poll_messages (line 572) | async def _poll_messages(self) -> None:
    method _build_message_event (line 601) | async def _build_message_event(self, data: Dict[str, Any]) -> Optional...

FILE: gateway/run.py
  function _ensure_ssl_certs (line 36) | def _ensure_ssl_certs() -> None:
  function _resolve_runtime_agent_kwargs (line 232) | def _resolve_runtime_agent_kwargs() -> dict:
  function _resolve_gateway_model (line 256) | def _resolve_gateway_model() -> str:
  function _resolve_hermes_bin (line 280) | def _resolve_hermes_bin() -> Optional[list[str]]:
  class GatewayRunner (line 307) | class GatewayRunner:
    method __init__ (line 315) | def __init__(self, config: Optional[GatewayConfig] = None):
    method _get_or_create_gateway_honcho (line 388) | def _get_or_create_gateway_honcho(self, session_key: str):
    method _shutdown_gateway_honcho (line 419) | def _shutdown_gateway_honcho(self, session_key: str) -> None:
    method _shutdown_all_gateway_honcho (line 435) | def _shutdown_all_gateway_honcho(self) -> None:
    method _has_setup_skill (line 445) | def _has_setup_skill(self) -> bool:
    method _load_voice_modes (line 457) | def _load_voice_modes(self) -> Dict[str, str]:
    method _save_voice_modes (line 473) | def _save_voice_modes(self) -> None:
    method _set_adapter_auto_tts_disabled (line 482) | def _set_adapter_auto_tts_disabled(self, adapter, chat_id: str, disabl...
    method _sync_voice_mode_state_to_adapter (line 492) | def _sync_voice_mode_state_to_adapter(self, adapter) -> None:
    method _flush_memories_for_session (line 504) | def _flush_memories_for_session(
    method _async_flush_memories (line 576) | async def _async_flush_memories(
    method should_exit_cleanly (line 591) | def should_exit_cleanly(self) -> bool:
    method exit_reason (line 595) | def exit_reason(self) -> Optional[str]:
    method _session_key_for_source (line 598) | def _session_key_for_source(self, source: SessionSource) -> str:
    method _resolve_turn_agent_config (line 613) | def _resolve_turn_agent_config(self, user_message: str, model: str, ru...
    method _handle_adapter_fatal_error (line 627) | async def _handle_adapter_fatal_error(self, adapter: BasePlatformAdapt...
    method _request_clean_exit (line 649) | def _request_clean_exit(self, reason: str) -> None:
    method _load_prefill_messages (line 655) | def _load_prefill_messages() -> List[Dict[str, Any]]:
    method _load_ephemeral_system_prompt (line 694) | def _load_ephemeral_system_prompt() -> str:
    method _load_reasoning_config (line 715) | def _load_reasoning_config() -> dict | None:
    method _load_show_reasoning (line 747) | def _load_show_reasoning() -> bool:
    method _load_background_notifications_mode (line 761) | def _load_background_notifications_mode() -> str:
    method _load_provider_routing (line 796) | def _load_provider_routing() -> dict:
    method _load_fallback_model (line 810) | def _load_fallback_model() -> dict | None:
    method _load_smart_model_routing (line 830) | def _load_smart_model_routing() -> dict:
    method start (line 843) | async def start(self) -> bool:
    method _session_expiry_watcher (line 1015) | async def _session_expiry_watcher(self, interval: int = 300):
    method stop (line 1053) | async def stop(self) -> None:
    method wait_for_shutdown (line 1094) | async def wait_for_shutdown(self) -> None:
    method _create_adapter (line 1098) | def _create_adapter(
    method _is_user_authorized (line 1205) | def _is_user_authorized(self, source: SessionSource) -> bool:
    method _get_unauthorized_dm_behavior (line 1284) | def _get_unauthorized_dm_behavior(self, platform: Optional[Platform]) ...
    method _handle_message (line 1291) | async def _handle_message(self, event: MessageEvent) -> Optional[str]:
    method _handle_message_with_agent (line 1622) | async def _handle_message_with_agent(self, event, source, _quick_key: ...
    method _handle_reset_command (line 2313) | async def _handle_reset_command(self, event: MessageEvent) -> str:
    method _handle_status_command (line 2357) | async def _handle_status_command(self, event: MessageEvent) -> str:
    method _handle_stop_command (line 2382) | async def _handle_stop_command(self, event: MessageEvent) -> str:
    method _handle_help_command (line 2397) | async def _handle_help_command(self, event: MessageEvent) -> str:
    method _handle_model_command (line 2415) | async def _handle_model_command(self, event: MessageEvent) -> str:
    method _handle_provider_command (line 2610) | async def _handle_provider_command(self, event: MessageEvent) -> str:
    method _handle_personality_command (line 2664) | async def _handle_personality_command(self, event: MessageEvent) -> str:
    method _handle_retry_command (line 2740) | async def _handle_retry_command(self, event: MessageEvent) -> str:
    method _handle_undo_command (line 2775) | async def _handle_undo_command(self, event: MessageEvent) -> str:
    method _handle_set_home_command (line 2800) | async def _handle_set_home_command(self, event: MessageEvent) -> str:
    method _get_guild_id (line 2831) | def _get_guild_id(event: MessageEvent) -> Optional[int]:
    method _handle_voice_command (line 2844) | async def _handle_voice_command(self, event: MessageEvent) -> str:
    method _handle_voice_channel_join (line 2919) | async def _handle_voice_channel_join(self, event: MessageEvent) -> str:
    method _handle_voice_channel_leave (line 2969) | async def _handle_voice_channel_leave(self, event: MessageEvent) -> str:
    method _handle_voice_timeout_cleanup (line 2992) | def _handle_voice_timeout_cleanup(self, chat_id: str) -> None:
    method _handle_voice_channel_input (line 3002) | async def _handle_voice_channel_input(
    method _should_send_voice_reply (line 3052) | def _should_send_voice_reply(
    method _send_voice_reply (line 3101) | async def _send_voice_reply(self, event: MessageEvent, text: str) -> N...
    method _handle_rollback_command (line 3159) | async def _handle_rollback_command(self, event: MessageEvent) -> str:
    method _handle_background_command (line 3218) | async def _handle_background_command(self, event: MessageEvent) -> str:
    method _run_background_task (line 3245) | async def _run_background_task(
    method _handle_reasoning_command (line 3414) | async def _handle_reasoning_command(self, event: MessageEvent) -> str:
    method _handle_compress_command (line 3498) | async def _handle_compress_command(self, event: MessageEvent) -> str:
    method _handle_title_command (line 3557) | async def _handle_title_command(self, event: MessageEvent) -> str:
    method _handle_resume_command (line 3591) | async def _handle_resume_command(self, event: MessageEvent) -> str:
    method _handle_usage_command (line 3668) | async def _handle_usage_command(self, event: MessageEvent) -> str:
    method _handle_insights_command (line 3705) | async def _handle_insights_command(self, event: MessageEvent) -> str:
    method _handle_reload_mcp_command (line 3752) | async def _handle_reload_mcp_command(self, event: MessageEvent) -> str:
    method _handle_approve_command (line 3828) | async def _handle_approve_command(self, event: MessageEvent) -> str:
    method _handle_deny_command (line 3882) | async def _handle_deny_command(self, event: MessageEvent) -> str:
    method _handle_update_command (line 3894) | async def _handle_update_command(self, event: MessageEvent) -> str:
    method _schedule_update_notification_watch (line 3968) | def _schedule_update_notification_watch(self) -> None:
    method _watch_for_update_completion (line 3981) | async def _watch_for_update_completion(
    method _send_update_notification (line 4004) | async def _send_update_notification(self) -> bool:
    method _set_session_env (line 4089) | def _set_session_env(self, context: SessionContext) -> None:
    method _clear_session_env (line 4098) | def _clear_session_env(self) -> None:
    method _enrich_message_with_vision (line 4104) | async def _enrich_message_with_vision(
    method _enrich_message_with_transcription (line 4172) | async def _enrich_message_with_transcription(
    method _run_process_watcher (line 4255) | async def _run_process_watcher(self, watcher: dict) -> None:
    method _run_agent (line 4352) | async def _run_agent(
  function _start_cron_ticker (line 5069) | def _start_cron_ticker(stop_event: threading.Event, adapters=None, inter...
  function start_gateway (line 5120) | async def start_gateway(config: Optional[GatewayConfig] = None, replace:...
  function main (line 5278) | def main():

FILE: gateway/session.py
  function _hash_id (line 32) | def _hash_id(value: str) -> str:
  function _hash_sender_id (line 37) | def _hash_sender_id(value: str) -> str:
  function _hash_chat_id (line 42) | def _hash_chat_id(value: str) -> str:
  function _looks_like_phone (line 55) | def _looks_like_phone(value: str) -> bool:
  class SessionSource (line 68) | class SessionSource:
    method description (line 89) | def description(self) -> str:
    method to_dict (line 109) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 127) | def from_dict(cls, data: Dict[str, Any]) -> "SessionSource":
    method local_cli (line 142) | def local_cli(cls) -> "SessionSource":
  class SessionContext (line 153) | class SessionContext:
    method to_dict (line 172) | def to_dict(self) -> Dict[str, Any]:
  function build_session_context_prompt (line 196) | def build_session_context_prompt(
  class SessionEntry (line 324) | class SessionEntry:
    method to_dict (line 359) | def to_dict(self) -> Dict[str, Any]:
    method from_dict (line 382) | def from_dict(cls, data: Dict[str, Any]) -> "SessionEntry":
  function build_session_key (line 414) | def build_session_key(source: SessionSource, group_sessions_per_user: bo...
  class SessionStore (line 457) | class SessionStore:
    method __init__ (line 465) | def __init__(self, sessions_dir: Path, config: GatewayConfig,
    method _ensure_loaded (line 485) | def _ensure_loaded(self) -> None:
    method _save (line 508) | def _save(self) -> None:
    method _generate_session_key (line 531) | def _generate_session_key(self, source: SessionSource) -> str:
    method _is_session_expired (line 538) | def _is_session_expired(self, entry: SessionEntry) -> bool:
    method _should_reset (line 576) | def _should_reset(self, entry: SessionEntry, source: SessionSource) ->...
    method has_any_sessions (line 617) | def has_any_sessions(self) -> bool:
    method get_or_create_session (line 638) | def get_or_create_session(
    method update_session (line 706) | def update_session(
    method reset_session (line 763) | def reset_session(self, session_key: str) -> Optional[SessionEntry]:
    method switch_session (line 809) | def switch_session(self, session_key: str, target_session_id: str) -> ...
    method list_sessions (line 851) | def list_sessions(self, active_minutes: Optional[int] = None) -> List[...
    method get_transcript_path (line 865) | def get_transcript_path(self, session_id: str) -> Path:
    method append_to_transcript (line 869) | def append_to_transcript(self, session_id: str, message: Dict[str, Any...
    method rewrite_transcript (line 897) | def rewrite_transcript(self, session_id: str, messages: List[Dict[str,...
    method load_transcript (line 925) | def load_transcript(self, session_id: str) -> List[Dict[str, Any]]:
  function build_session_context (line 958) | def build_session_context(

FILE: gateway/status.py
  function _get_pid_path (line 27) | def _get_pid_path() -> Path:
  function _get_runtime_status_path (line 33) | def _get_runtime_status_path() -> Path:
  function _get_lock_dir (line 38) | def _get_lock_dir() -> Path:
  function _utc_now_iso (line 47) | def _utc_now_iso() -> str:
  function _scope_hash (line 51) | def _scope_hash(identity: str) -> str:
  function _get_scope_lock_path (line 55) | def _get_scope_lock_path(scope: str, identity: str) -> Path:
  function _get_process_start_time (line 59) | def _get_process_start_time(pid: int) -> Optional[int]:
  function _read_process_cmdline (line 69) | def _read_process_cmdline(pid: int) -> Optional[str]:
  function _looks_like_gateway_process (line 82) | def _looks_like_gateway_process(pid: int) -> bool:
  function _record_looks_like_gateway (line 97) | def _record_looks_like_gateway(record: dict[str, Any]) -> bool:
  function _build_pid_record (line 116) | def _build_pid_record() -> dict:
  function _build_runtime_status_record (line 125) | def _build_runtime_status_record() -> dict[str, Any]:
  function _read_json_file (line 136) | def _read_json_file(path: Path) -> Optional[dict[str, Any]]:
  function _write_json_file (line 152) | def _write_json_file(path: Path, payload: dict[str, Any]) -> None:
  function _read_pid_record (line 157) | def _read_pid_record() -> Optional[dict]:
  function write_pid_file (line 181) | def write_pid_file() -> None:
  function write_runtime_status (line 186) | def write_runtime_status(
  function read_runtime_status (line 223) | def read_runtime_status() -> Optional[dict[str, Any]]:
  function remove_pid_file (line 228) | def remove_pid_file() -> None:
  function acquire_scoped_lock (line 236) | def acquire_scoped_lock(scope: str, identity: str, metadata: Optional[di...
  function release_scoped_lock (line 301) | def release_scoped_lock(scope: str, identity: str) -> None:
  function get_running_pid (line 317) | def get_running_pid() -> Optional[int]:
  function is_gateway_running (line 354) | def is_gateway_running() -> bool:

FILE: gateway/sticker_cache.py
  function _load_cache (line 29) | def _load_cache() -> dict:
  function _save_cache (line 39) | def _save_cache(cache: dict) -> None:
  function get_cached_description (line 48) | def get_cached_description(file_unique_id: str) -> Optional[dict]:
  function cache_sticker_description (line 59) | def cache_sticker_description(
  function build_sticker_injection (line 84) | def build_sticker_injection(
  function build_animated_sticker_injection (line 104) | def build_animated_sticker_injection(emoji: str = "") -> str:

FILE: gateway/stream_consumer.py
  class StreamConsumerConfig (line 32) | class StreamConsumerConfig:
  class GatewayStreamConsumer (line 39) | class GatewayStreamConsumer:
    method __init__ (line 54) | def __init__(
    method already_sent (line 74) | def already_sent(self) -> bool:
    method on_delta (line 79) | def on_delta(self, text: str) -> None:
    method finish (line 84) | def finish(self) -> None:
    method run (line 88) | async def run(self) -> None:
    method _send_or_edit (line 159) | async def _send_or_edit(self, text: str) -> None:

FILE: hermes_cli/auth.py
  class ProviderConfig (line 82) | class ProviderConfig:
  function _resolve_kimi_base_url (line 229) | def _resolve_kimi_base_url(api_key: str, default_url: str, env_override:...
  function _gh_cli_candidates (line 242) | def _gh_cli_candidates() -> list[str]:
  function _try_gh_cli_token (line 263) | def _try_gh_cli_token() -> Optional[str]:
  function _resolve_api_key_provider_secret (line 281) | def _resolve_api_key_provider_secret(
  function detect_zai_endpoint (line 323) | def detect_zai_endpoint(api_key: str, timeout: float = 8.0) -> Optional[...
  class AuthError (line 363) | class AuthError(RuntimeError):
    method __init__ (line 366) | def __init__(
  function format_auth_error (line 380) | def format_auth_error(error: Exception) -> str:
  function _token_fingerprint (line 406) | def _token_fingerprint(token: Any) -> Optional[str]:
  function _oauth_trace_enabled (line 416) | def _oauth_trace_enabled() -> bool:
  function _oauth_trace (line 421) | def _oauth_trace(event: str, *, sequence_id: Optional[str] = None, **fie...
  function _auth_file_path (line 435) | def _auth_file_path() -> Path:
  function _auth_lock_path (line 439) | def _auth_lock_path() -> Path:
  function _auth_store_lock (line 446) | def _auth_store_lock(timeout_seconds: float = AUTH_LOCK_TIMEOUT_SECONDS):
  function _load_auth_store (line 503) | def _load_auth_store(auth_file: Optional[Path] = None) -> Dict[str, Any]:
  function _save_auth_store (line 528) | def _save_auth_store(auth_store: Dict[str, Any]) -> Path:
  function _load_provider_state (line 564) | def _load_provider_state(auth_store: Dict[str, Any], provider_id: str) -...
  function _save_provider_state (line 572) | def _save_provider_state(auth_store: Dict[str, Any], provider_id: str, s...
  function get_provider_auth_state (line 581) | def get_provider_auth_state(provider_id: str) -> Optional[Dict[str, Any]]:
  function get_active_provider (line 587) | def get_active_provider() -> Optional[str]:
  function clear_provider_auth (line 593) | def clear_provider_auth(provider_id: Optional[str] = None) -> bool:
  function deactivate_provider (line 616) | def deactivate_provider() -> None:
  function resolve_provider (line 632) | def resolve_provider(
  function _parse_iso_timestamp (line 714) | def _parse_iso_timestamp(value: Any) -> Optional[float]:
  function _is_expiring (line 731) | def _is_expiring(expires_at_iso: Any, skew_seconds: int) -> bool:
  function _coerce_ttl_seconds (line 738) | def _coerce_ttl_seconds(expires_in: Any) -> int:
  function _optional_base_url (line 746) | def _optional_base_url(value: Any) -> Optional[str]:
  function _decode_jwt_claims (line 753) | def _decode_jwt_claims(token: Any) -> Dict[str, Any]:
  function _codex_access_token_is_expiring (line 766) | def _codex_access_token_is_expiring(access_token: Any, skew_seconds: int...
  function _is_remote_session (line 778) | def _is_remote_session() -> bool:
  function _read_codex_tokens (line 791) | def _read_codex_tokens(*, _lock: bool = True) -> Dict[str, Any]:
  function _save_codex_tokens (line 840) | def _save_codex_tokens(tokens: Dict[str, str], last_refresh: str = None)...
  function _refresh_codex_auth_tokens (line 854) | def _refresh_codex_auth_tokens(
  function _import_codex_cli_tokens (line 936) | def _import_codex_cli_tokens() -> Optional[Dict[str, str]]:
  function resolve_codex_runtime_credentials (line 959) | def resolve_codex_runtime_credentials(
  function _resolve_verify (line 1026) | def _resolve_verify(
  function _request_device_code (line 1057) | def _request_device_code(
  function _poll_for_token (line 1084) | def _poll_for_token(
  function _refresh_access_token (line 1137) | def _refresh_access_token(
  function _mint_agent_key (line 1172) | def _mint_agent_key(
  function fetch_nous_models (line 1205) | def fetch_nous_models(
  function _agent_key_is_usable (line 1262) | def _agent_key_is_usable(state: Dict[str, Any], min_ttl_seconds: int) ->...
  function resolve_nous_runtime_credentials (line 1269) | def resolve_nous_runtime_credentials(
  function get_nous_auth_status (line 1522) | def get_nous_auth_status() -> Dict[str, Any]:
  function get_codex_auth_status (line 1544) | def get_codex_auth_status() -> Dict[str, Any]:
  function get_api_key_provider_status (line 1563) | def get_api_key_provider_status(provider_id: str) -> Dict[str, Any]:
  function get_external_process_provider_status (line 1594) | def get_external_process_provider_status(provider_id: str) -> Dict[str, ...
  function get_auth_status (line 1624) | def get_auth_status(provider_id: Optional[str] = None) -> Dict[str, Any]:
  function resolve_api_key_provider_credentials (line 1640) | def resolve_api_key_provider_credentials(provider_id: str) -> Dict[str, ...
  function resolve_external_process_provider_credentials (line 1676) | def resolve_external_process_provider_credentials(provider_id: str) -> D...
  function detect_external_credentials (line 1720) | def detect_external_credentials() -> List[Dict[str, Any]]:
  function _update_config_for_provider (line 1747) | def _update_config_for_provider(
  function _reset_config_provider (line 1809) | def _reset_config_provider() -> Path:
  function _prompt_model_selection (line 1832) | def _prompt_model_selection(model_ids: List[str], current_model: str = "...
  function _save_model_choice (line 1909) | def _save_model_choice(model_id: str) -> None:
  function login_command (line 1926) | def login_command(args) -> None:
  function _login_openai_codex (line 1934) | def _login_openai_codex(args, pconfig: ProviderConfig) -> None:
  function _codex_device_code_login (line 1990) | def _codex_device_code_login() -> Dict[str, Any]:
  function _login_nous (line 2135) | def _login_nous(args, pconfig: ProviderConfig) -> None:
  function logout_command (line 2293) | def logout_command(args) -> None:

FILE: hermes_cli/banner.py
  function cprint (line 36) | def cprint(text: str):
  function _skin_color (line 45) | def _skin_color(key: str, fallback: str) -> str:
  function _skin_branding (line 54) | def _skin_branding(key: str, fallback: str) -> str:
  function get_available_skills (line 104) | def get_available_skills() -> Dict[str, List[str]]:
  function check_for_updates (line 132) | def check_for_updates() -> Optional[int]:
  function prefetch_update_check (line 200) | def prefetch_update_check():
  function get_update_result (line 210) | def get_update_result(timeout: float = 0.5) -> Optional[int]:
  function _format_context_length (line 220) | def _format_context_length(tokens: int) -> str:
  function _display_toolset_name (line 231) | def _display_toolset_name(toolset_name: str) -> str:
  function build_welcome_banner (line 242) | def build_welcome_banner(console: Console, model: str, cwd: str,

FILE: hermes_cli/callbacks.py
  function clarify_callback (line 17) | def clarify_callback(cli, question, choices):
  function sudo_password_callback (line 65) | def sudo_password_callback(cli) -> str:
  function prompt_for_secret (line 106) | def prompt_for_secret(cli, var_name: str, prompt: str, metadata=None) ->...
  function approval_callback (line 224) | def approval_callback(cli, command: str, description: str) -> str:

FILE: hermes_cli/checklist.py
  function curses_checklist (line 13) | def curses_checklist(

FILE: hermes_cli/claw.py
  function _find_migration_script (line 51) | def _find_migration_script() -> Path | None:
  function _load_migration_module (line 59) | def _load_migration_module(script_path: Path):
  function claw_command (line 76) | def claw_command(args):
  function _cmd_migrate (line 91) | def _cmd_migrate(args):
  function _print_migration_report (line 204) | def _print_migration_report(report: dict, dry_run: bool):

FILE: hermes_cli/clipboard.py
  function save_clipboard_image (line 27) | def save_clipboard_image(dest: Path) -> bool:
  function has_clipboard_image (line 38) | def has_clipboard_image() -> bool:
  function _macos_save (line 54) | def _macos_save(dest: Path) -> bool:
  function _macos_has_image (line 59) | def _macos_has_image() -> bool:
  function _macos_pngpaste (line 71) | def _macos_pngpaste(dest: Path) -> bool:
  function _macos_osascript (line 87) | def _macos_osascript(dest: Path) -> bool:
  function _is_wsl (line 117) | def _is_wsl() -> bool:
  function _linux_save (line 130) | def _linux_save(dest: Path) -> bool:
  function _wsl_has_image (line 164) | def _wsl_has_image() -> bool:
  function _wsl_save (line 180) | def _wsl_save(dest: Path) -> bool:
  function _wayland_has_image (line 209) | def _wayland_has_image() -> bool:
  function _wayland_save (line 226) | def _wayland_save(dest: Path) -> bool:
  function _convert_to_png (line 275) | def _convert_to_png(path: Path) -> bool:
  function _xclip_has_image (line 317) | def _xclip_has_image() -> bool:
  function _xclip_save (line 332) | def _xclip_save(dest: Path) -> bool:

FILE: hermes_cli/codex_models.py
  function _add_forward_compat_models (line 28) | def _add_forward_compat_models(model_ids: List[str]) -> List[str]:
  function _fetch_models_from_api (line 52) | def _fetch_models_from_api(access_token: str) -> List[str]:
  function _read_default_model (line 90) | def _read_default_model(codex_home: Path) -> Optional[str]:
  function _read_cache_models (line 108) | def _read_cache_models(codex_home: Path) -> List[str]:
  function get_codex_model_ids (line 144) | def get_codex_model_ids(access_token: Optional[str] = None) -> List[str]:

FILE: hermes_cli/colors.py
  class Colors (line 6) | class Colors:
  function color (line 18) | def color(text: str, *codes) -> str:

FILE: hermes_cli/commands.py
  class CommandDef (line 29) | class CommandDef:
  function _build_command_lookup (line 143) | def _build_command_lookup() -> dict[str, CommandDef]:
  function resolve_command (line 156) | def resolve_command(name: str) -> CommandDef | None:
  function _build_description (line 164) | def _build_description(cmd: CommandDef) -> str:
  function gateway_help_lines (line 222) | def gateway_help_lines() -> list[str]:
  function telegram_bot_commands (line 240) | def telegram_bot_commands() -> list[tuple[str, str]]:
  function slack_subcommand_map (line 256) | def slack_subcommand_map() -> dict[str, str]:
  class SlashCommandCompleter (line 276) | class SlashCommandCompleter(Completer):
    method __init__ (line 279) | def __init__(
    method _get_model_info (line 291) | def _get_model_info(self) -> dict[str, Any]:
    method _iter_skill_commands (line 306) | def _iter_skill_commands(self) -> Mapping[str, dict[str, Any]]:
    method _completion_text (line 315) | def _completion_text(cmd_name: str, word: str) -> str:
    method _extract_path_word (line 326) | def _extract_path_word(text: str) -> str | None:
    method _path_completions (line 350) | def _path_completions(word: str, limit: int = 30):
    method get_completions (line 400) | def get_completions(self, document, complete_event):
  class SlashCommandAutoSuggest (line 502) | class SlashCommandAutoSuggest(AutoSuggest):
    method __init__ (line 509) | def __init__(
    method get_suggestion (line 517) | def get_suggestion(self, buffer, document):
  function _file_size_label (line 582) | def _file_size_label(path: str) -> str:

FILE: hermes_cli/config.py
  function get_hermes_home (line 53) | def get_hermes_home() -> Path:
  function get_config_path (line 57) | def get_config_path() -> Path:
  function get_env_path (line 61) | def get_env_path() -> Path:
  function get_project_root (line 65) | def get_project_root() -> Path:
  function _secure_dir (line 69) | def _secure_dir(path):
  function _secure_file (line 77) | def _secure_file(path):
  function _ensure_default_soul_md (line 86) | def _ensure_default_soul_md(home: Path) -> None:
  function ensure_hermes_home (line 95) | def ensure_hermes_home():
  function get_missing_env_vars (line 893) | def get_missing_env_vars(required_only: bool = False) -> List[Dict[str, ...
  function _set_nested (line 915) | def _set_nested(config: dict, dotted_key: str, value):
  function get_missing_config_fields (line 930) | def get_missing_config_fields() -> List[Dict[str, Any]]:
  function check_config_version (line 958) | def check_config_version() -> Tuple[int, int]:
  function migrate_config (line 970) | def migrate_config(interactive: bool = True, quiet: bool = False) -> Dic...
  function _deep_merge (line 1154) | def _deep_merge(base: dict, override: dict) -> dict:
  function _normalize_max_turns_config (line 1174) | def _normalize_max_turns_config(config: Dict[str, Any]) -> Dict[str, Any]:
  function load_config (line 1191) | def load_config() -> Dict[str, Any]:
  function save_config (line 1313) | def save_config(config: Dict[str, Any]):
  function load_env (line 1339) | def load_env() -> Dict[str, str]:
  function _sanitize_env_lines (line 1358) | def _sanitize_env_lines(lines: list) -> list:
  function sanitize_env_file (line 1409) | def sanitize_env_file() -> int:
  function save_env_value (line 1454) | def save_env_value(key: str, value: str):
  function save_anthropic_oauth_token (line 1513) | def save_anthropic_oauth_token(value: str, save_fn=None):
  function use_anthropic_claude_code_credentials (line 1520) | def use_anthropic_claude_code_credentials(save_fn=None):
  function save_anthropic_api_key (line 1527) | def save_anthropic_api_key(value: str, save_fn=None):
  function save_env_value_secure (line 1534) | def save_env_value_secure(key: str, value: str) -> Dict[str, Any]:
  function get_env_value (line 1544) | def get_env_value(key: str) -> Optional[str]:
  function redact_key (line 1559) | def redact_key(key: str) -> str:
  function show_config (line 1568) | def show_config():
  function edit_config (line 1707) | def edit_config():
  function set_config_value (line 1736) | def set_config_value(key: str, value: str):
  function config_command (line 1817) | def config_command(args):

FILE: hermes_cli/copilot_auth.py
  function is_classic_pat (line 54) | def is_classic_pat(token: str) -> bool:
  function validate_copilot_token (line 59) | def validate_copilot_token(token: str) -> tuple[bool, str]:
  function resolve_copilot_token (line 80) | def resolve_copilot_token() -> tuple[str, str]:
  function _gh_cli_candidates (line 111) | def _gh_cli_candidates() -> list[str]:
  function _try_gh_cli_token (line 132) | def _try_gh_cli_token() -> Optional[str]:
  function copilot_device_code_login (line 152) | def copilot_device_code_login(
  function copilot_request_headers (line 277) | def copilot_request_headers(

FILE: hermes_cli/cron.py
  function _normalize_skills (line 19) | def _normalize_skills(single_skill=None, skills: Optional[Iterable[str]]...
  function _cron_api (line 35) | def _cron_api(**kwargs):
  function cron_list (line 41) | def cron_list(show_all: bool = False):
  function cron_tick (line 103) | def cron_tick():
  function cron_status (line 109) | def cron_status():
  function cron_create (line 142) | def cron_create(args):
  function cron_edit (line 165) | def cron_edit(args):
  function _job_action (line 214) | def _job_action(action: str, job_id: str, success_verb: str) -> int:
  function cron_command (line 228) | def cron_command(args):

FILE: hermes_cli/curses_ui.py
  function curses_checklist (line 12) | def curses_checklist(
  function _numbered_fallback (line 113) | def _numbered_fallback(

FILE: hermes_cli/doctor.py
  function _has_provider_env_config (line 53) | def _has_provider_env_config(content: str) -> bool:
  function _honcho_is_configured_for_doctor (line 58) | def _honcho_is_configured_for_doctor() -> bool:
  function _apply_doctor_tool_availability_overrides (line 69) | def _apply_doctor_tool_availability_overrides(available: list[str], unav...
  function check_ok (line 85) | def check_ok(text: str, detail: str = ""):
  function check_warn (line 88) | def check_warn(text: str, detail: str = ""):
  function check_fail (line 91) | def check_fail(text: str, detail: str = ""):
  function check_info (line 94) | def check_info(text: str):
  function _check_gateway_service_linger (line 98) | def _check_gateway_service_linger(issues: list[str]) -> None:
  function run_doctor (line 131) | def run_doctor(args):

FILE: hermes_cli/env_loader.py
  function _load_dotenv_with_fallback (line 12) | def _load_dotenv_with_fallback(path: Path, *, override: bool) -> None:
  function load_hermes_dotenv (line 19) | def load_hermes_dotenv(

FILE: hermes_cli/gateway.py
  function find_gateway_pids (line 29) | def find_gateway_pids() -> list:
  function kill_gateway_processes (line 89) | def kill_gateway_processes(force: bool = False) -> int:
  function is_linux (line 110) | def is_linux() -> bool:
  function is_macos (line 113) | def is_macos() -> bool:
  function is_windows (line 116) | def is_windows() -> bool:
  function get_service_name (line 128) | def get_service_name() -> str:
  function get_systemd_unit_path (line 148) | def get_systemd_unit_path(system: bool = False) -> Path:
  function _ensure_user_systemd_env (line 155) | def _ensure_user_systemd_env() -> None:
  function _systemctl_cmd (line 177) | def _systemctl_cmd(system: bool = False) -> list[str]:
  function _journalctl_cmd (line 183) | def _journalctl_cmd(system: bool = False) -> list[str]:
  function _service_scope_label (line 187) | def _service_scope_label(system: bool = False) -> str:
  function get_installed_systemd_scopes (line 191) | def get_installed_systemd_scopes() -> list[str]:
  function has_conflicting_systemd_units (line 204) | def has_conflicting_systemd_units() -> bool:
  function print_systemd_scope_conflict_warning (line 208) | def print_systemd_scope_conflict_warning() -> None:
  function _require_root_for_system_service (line 222) | def _require_root_for_system_service(action: str) -> None:
  function _system_service_identity (line 228) | def _system_service_identity(run_as_user: str | None = None) -> tuple[st...
  function _read_systemd_user_from_unit (line 248) | def _read_systemd_user_from_unit(unit_path: Path) -> str | None:
  function _default_system_service_user (line 259) | def _default_system_service_user() -> str | None:
  function prompt_linux_gateway_install_scope (line 266) | def prompt_linux_gateway_install_scope() -> str | None:
  function install_linux_gateway_from_setup (line 279) | def install_linux_gateway_from_setup(force: bool = False) -> tuple[str |...
  function get_systemd_linger_status (line 310) | def get_systemd_linger_status() -> tuple[bool | None, str]:
  function print_systemd_linger_guidance (line 358) | def print_systemd_linger_guidance() -> None:
  function get_launchd_plist_path (line 371) | def get_launchd_plist_path() -> Path:
  function get_python_path (line 374) | def get_python_path() -> str:
  function get_hermes_cli_path (line 383) | def get_hermes_cli_path() -> str:
  function generate_systemd_unit (line 399) | def generate_systemd_unit(system: bool = False, run_as_user: str | None ...
  function _normalize_service_definition (line 471) | def _normalize_service_definition(text: str) -> str:
  function systemd_unit_is_current (line 475) | def systemd_unit_is_current(system: bool = False) -> bool:
  function refresh_systemd_unit_if_needed (line 487) | def refresh_systemd_unit_if_needed(system: bool = False) -> bool:
  function _print_linger_enable_warning (line 501) | def _print_linger_enable_warning(username: str, detail: str | None = Non...
  function _ensure_linger_enabled (line 516) | def _ensure_linger_enabled() -> None:
  function _select_systemd_scope (line 559) | def _select_systemd_scope(system: bool = False) -> bool:
  function systemd_install (line 565) | def systemd_install(force: bool = False, system: bool = False, run_as_us...
  function systemd_uninstall (line 609) | def systemd_uninstall(system: bool = False):
  function systemd_start (line 626) | def systemd_start(system: bool = False):
  function systemd_stop (line 636) | def systemd_stop(system: bool = False):
  function systemd_restart (line 645) | def systemd_restart(system: bool = False):
  function systemd_status (line 655) | def systemd_status(deep: bool = False, system: bool = False):
  function generate_launchd_plist (line 726) | def generate_launchd_plist() -> str:
  function launchd_plist_is_current (line 770) | def launchd_plist_is_current() -> bool:
  function refresh_launchd_plist_if_needed (line 781) | def refresh_launchd_plist_if_needed() -> bool:
  function launchd_install (line 800) | def launchd_install(force: bool = False):
  function launchd_uninstall (line 826) | def launchd_uninstall():
  function launchd_start (line 836) | def launchd_start():
  function launchd_stop (line 849) | def launchd_stop():
  function _wait_for_gateway_exit (line 853) | def _wait_for_gateway_exit(timeout: float = 10.0, force_after: float = 5...
  function launchd_restart (line 893) | def launchd_restart():
  function launchd_status (line 903) | def launchd_status(deep: bool = False):
  function run_gateway (line 938) | def run_gateway(verbose: bool = False, replace: bool = False):
  function _platform_status (line 1197) | def _platform_status(platform: dict) -> str:
  function _runtime_health_lines (line 1243) | def _runtime_health_lines() -> list[str]:
  function _setup_standard_platform (line 1272) | def _setup_standard_platform(platform: dict):
  function _setup_whatsapp (line 1370) | def _setup_whatsapp():
  function _is_service_installed (line 1377) | def _is_service_installed() -> bool:
  function _is_service_running (line 1386) | def _is_service_running() -> bool:
  function _setup_signal (line 1419) | def _setup_signal():
  function gateway_setup (line 1534) | def gateway_setup():
  function gateway_command (line 1673) | def gateway_command(args):

FILE: hermes_cli/main.py
  function _relative_time (line 77) | def _relative_time(ts) -> str:
  function _has_any_provider_configured (line 95) | def _has_any_provider_configured() -> bool:
  function _session_browse_picker (line 168) | def _session_browse_picker(sessions: list) -> Optional[str]:
  function _resolve_last_cli_session (line 402) | def _resolve_last_cli_session() -> Optional[str]:
  function _resolve_session_by_name_or_id (line 416) | def _resolve_session_by_name_or_id(name_or_id: str) -> Optional[str]:
  function cmd_chat (line 442) | def cmd_chat(args):
  function cmd_gateway (line 546) | def cmd_gateway(args):
  function cmd_whatsapp (line 552) | def cmd_whatsapp(args):
  function cmd_setup (line 739) | def cmd_setup(args):
  function cmd_model (line 745) | def cmd_model(args):
  function _prompt_provider_choice (line 903) | def _prompt_provider_choice(choices):
  function _model_flow_openrouter (line 942) | def _model_flow_openrouter(config, current_model=""):
  function _model_flow_nous (line 991) | def _model_flow_nous(config, current_model=""):
  function _model_flow_openai_codex (line 1068) | def _model_flow_openai_codex(config, current_model=""):
  function _model_flow_custom (line 1117) | def _model_flow_custom(config):
  function _save_custom_provider (line 1220) | def _save_custom_provider(base_url, api_key="", model="", context_length...
  function _remove_custom_provider (line 1282) | def _remove_custom_provider(config):
  function _model_flow_named_custom (line 1337) | def _model_flow_named_custom(config, provider_info):
  function _current_reasoning_effort (line 1511) | def _current_reasoning_effort(config) -> str:
  function _set_reasoning_effort (line 1518) | def _set_reasoning_effort(config, effort: str) -> None:
  function _prompt_reasoning_effort_selection (line 1526) | def _prompt_reasoning_effort_selection(efforts, current_effort=""):
  function _model_flow_copilot (line 1604) | def _model_flow_copilot(config, current_model=""):
  function _model_flow_copilot_acp (line 1780) | def _model_flow_copilot_acp(config, current_model=""):
  function _model_flow_kimi (line 1881) | def _model_flow_kimi(config, current_model=""):
  function _model_flow_api_key_provider (line 1985) | def _model_flow_api_key_provider(config, provider_id, current_model=""):
  function _run_anthropic_oauth_flow (line 2084) | def _run_anthropic_oauth_flow(save_env_value):
  function _model_flow_anthropic (line 2168) | def _model_flow_anthropic(config, current_model=""):
  function cmd_login (line 2301) | def cmd_login(args):
  function cmd_logout (line 2307) | def cmd_logout(args):
  function cmd_status (line 2313) | def cmd_status(args):
  function cmd_cron (line 2319) | def cmd_cron(args):
  function cmd_doctor (line 2325) | def cmd_doctor(args):
  function cmd_config (line 2331) | def cmd_config(args):
  function cmd_version (line 2337) | def cmd_version(args):
  function cmd_uninstall (line 2365) | def cmd_uninstall(args):
  function _update_via_zip (line 2371) | def _update_via_zip(args):
  function _stash_local_changes_if_needed (line 2479) | def _stash_local_changes_if_needed(git_cmd: list[str], cwd: Path) -> Opt...
  function _resolve_stash_selector (line 2510) | def _resolve_stash_selector(git_cmd: list[str], cwd: Path, stash_ref: st...
  function _print_stash_cleanup_guidance (line 2526) | def _print_stash_cleanup_guidance(stash_ref: str, stash_selector: Option...
  function _restore_stashed_changes (line 2536) | def _restore_stashed_changes(
  function _invalidate_update_cache (line 2597) | def _invalidate_update_cache():
  function cmd_update (line 2609) | def cmd_update(args):
  function _coalesce_session_name_args (line 2930) | def _coalesce_session_name_args(argv: list) -> list:
  function main (line 2968) | def main():

FILE: hermes_cli/models.py
  function model_ids (line 257) | def model_ids() -> list[str]:
  function menu_labels (line 262) | def menu_labels() -> list[str]:
  function list_available_providers (line 278) | def list_available_providers() -> list[dict[str, str]]:
  function parse_model_input (line 320) | def parse_model_input(raw: str, current_provider: str) -> tuple[str, str]:
  function _get_custom_base_url (line 347) | def _get_custom_base_url() -> str:
  function curated_models_for_provider (line 360) | def curated_models_for_provider(provider: Optional[str]) -> list[tuple[s...
  function detect_provider_for_model (line 381) | def detect_provider_for_model(
  function _find_openrouter_slug (line 475) | def _find_openrouter_slug(model_name: str) -> Optional[str]:
  function normalize_provider (line 502) | def normalize_provider(provider: Optional[str]) -> str:
  function provider_label (line 513) | def provider_label(provider: Optional[str]) -> str:
  function _resolve_copilot_catalog_api_key (line 523) | def _resolve_copilot_catalog_api_key() -> str:
  function provider_model_ids (line 534) | def provider_model_ids(provider: Optional[str]) -> list[str]:
  function _fetch_anthropic_models (line 590) | def _fetch_anthropic_models(timeout: float = 5.0) -> Optional[list[str]]:
  function _payload_items (line 634) | def _payload_items(payload: Any) -> list[dict[str, Any]]:
  function _extract_model_ids (line 644) | def _extract_model_ids(payload: Any) -> list[str]:
  function copilot_default_headers (line 648) | def copilot_default_headers() -> dict[str, str]:
  function _copilot_catalog_item_is_text_model (line 666) | def _copilot_catalog_item_is_text_model(item: dict[str, Any]) -> bool:
  function fetch_github_model_catalog (line 695) | def fetch_github_model_catalog(
  function _is_github_models_base_url (line 730) | def _is_github_models_base_url(base_url: Optional[str]) -> bool:
  function _fetch_github_models (line 738) | def _fetch_github_models(api_key: Optional[str] = None, timeout: float =...
  function _copilot_catalog_ids (line 768) | def _copilot_catalog_ids(
  function normalize_copilot_model_id (line 783) | def normalize_copilot_model_id(
  function _github_reasoning_efforts_for_model_id (line 824) | def _github_reasoning_efforts_for_model_id(model_id: str) -> list[str]:
  function _should_use_copilot_responses_api (line 834) | def _should_use_copilot_responses_api(model_id: str) -> bool:
  function copilot_model_api_mode (line 851) | def copilot_model_api_mode(
  function github_model_reasoning_efforts (line 890) | def github_model_reasoning_efforts(
  function probe_api_models (line 934) | def probe_api_models(
  function _fetch_ai_gateway_models (line 1002) | def _fetch_ai_gateway_models(timeout: float = 5.0) -> Optional[list[str]]:
  function fetch_api_models (line 1029) | def fetch_api_models(
  function validate_requested_model (line 1042) | def validate_requested_model(

FILE: hermes_cli/pairing.py
  function pairing_command (line 11) | def pairing_command(args):
  function _cmd_list (line 31) | def _cmd_list(store):
  function _cmd_approve (line 64) | def _cmd_approve(store, platform: str, code: str):
  function _cmd_revoke (line 81) | def _cmd_revoke(store, platform: str, user_id: str):
  function _cmd_clear_pending (line 91) | def _cmd_clear_pending(store):

FILE: hermes_cli/plugins.py
  class PluginManifest (line 70) | class PluginManifest:
  class LoadedPlugin (line 85) | class LoadedPlugin:
  class PluginContext (line 100) | class PluginContext:
    method __init__ (line 103) | def __init__(self, manifest: PluginManifest, manager: "PluginManager"):
    method register_tool (line 109) | def register_tool(
    method register_hook (line 140) | def register_hook(self, hook_name: str, callback: Callable) -> None:
  class PluginManager (line 162) | class PluginManager:
    method __init__ (line 165) | def __init__(self) -> None:
    method discover_and_load (line 175) | def discover_and_load(self) -> None:
    method _scan_directory (line 210) | def _scan_directory(self, path: Path, source: str) -> List[PluginManif...
    method _scan_entry_points (line 252) | def _scan_entry_points(self) -> List[PluginManifest]:
    method _load_plugin (line 281) | def _load_plugin(self, manifest: PluginManifest) -> None:
    method _load_directory_module (line 329) | def _load_directory_module(self, manifest: PluginManifest) -> types.Mo...
    method _load_entrypoint_module (line 359) | def _load_entrypoint_module(self, manifest: PluginManifest) -> types.M...
    method invoke_hook (line 381) | def invoke_hook(self, hook_name: str, **kwargs: Any) -> None:
    method list_plugins (line 403) | def list_plugins(self) -> List[Dict[str, Any]]:
  function get_plugin_manager (line 429) | def get_plugin_manager() -> PluginManager:
  function discover_plugins (line 437) | def discover_plugins() -> None:
  function invoke_hook (line 442) | def invoke_hook(hook_name: str, **kwargs: Any) -> None:
  function get_plugin_tool_names (line 447) | def get_plugin_tool_names() -> Set[str]:

FILE: hermes_cli/runtime_provider.py
  function _normalize_custom_provider_name (line 23) | def _normalize_custom_provider_name(value: str) -> str:
  function _detect_api_mode_for_url (line 27) | def _detect_api_mode_for_url(base_url: str) -> Optional[str]:
  function _auto_detect_local_model (line 39) | def _auto_detect_local_model(base_url: str) -> str:
  function _get_model_config (line 60) | def _get_model_config() -> Dict[str, Any]:
  function _copilot_runtime_api_mode (line 79) | def _copilot_runtime_api_mode(model_cfg: Dict[str, Any], api_key: str) -...
  function _parse_api_mode (line 99) | def _parse_api_mode(raw: Any) -> Optional[str]:
  function resolve_requested_provider (line 108) | def resolve_requested_provider(requested: Optional[str] = None) -> str:
  function _get_named_custom_provider (line 127) | def _get_named_custom_provider(requested_provider: str) -> Optional[Dict...
  function _resolve_named_custom_runtime (line 174) | def _resolve_named_custom_runtime(
  function _resolve_openrouter_runtime (line 209) | def _resolve_openrouter_runtime(
  function resolve_runtime_provider (line 289) | def resolve_runtime_provider(
  function format_runtime_provider_error (line 430) | def format_runtime_provider_error(error: Exception) -> str:

FILE: hermes_cli/setup.py
  function _model_config_dict (line 26) | def _model_config_dict(config: Dict[str, Any]) -> Dict[str, Any]:
  function _set_model_provider (line 35) | def _set_model_provider(
  function _set_default_model (line 47) | def _set_default_model(config: Dict[str, Any], model_name: str) -> None:
  function _current_reasoning_effort (line 86) | def _current_reasoning_effort(config: Dict[str, Any]) -> str:
  function _set_reasoning_effort (line 93) | def _set_reasoning_effort(config: Dict[str, Any], effort: str) -> None:
  function _setup_copilot_reasoning_selection (line 101) | def _setup_copilot_reasoning_selection(
  function _setup_provider_model_selection (line 139) | def _setup_provider_model_selection(config, provider_id, current_model, ...
  function _sync_model_from_disk (line 266) | def _sync_model_from_disk(config: Dict[str, Any]) -> None:
  function print_header (line 292) | def print_header(title: str):
  function print_info (line 298) | def print_info(text: str):
  function print_success (line 303) | def print_success(text: str):
  function print_warning (line 308) | def print_warning(text: str):
  function print_error (line 313) | def print_error(text: str):
  function is_interactive_stdin (line 318) | def is_interactive_stdin() -> bool:
  function print_noninteractive_setup_guidance (line 329) | def print_noninteractive_setup_guidance(reason: str | None = None) -> None:
  function prompt (line 348) | def prompt(question: str, default: str = None, password: bool = False) -...
  function _curses_prompt_choice (line 369) | def _curses_prompt_choice(question: str, choices: list, default: int = 0...
  function prompt_choice (line 433) | def prompt_choice(question: str, choices: list, default: int = 0) -> int:
  function prompt_yes_no (line 476) | def prompt_yes_no(question: str, default: bool = True) -> bool:
  function prompt_checklist (line 500) | def prompt_checklist(title: str, items: list, pre_selected: list = None)...
  function _prompt_api_key (line 529) | def _prompt_api_key(var: dict):
  function _print_setup_summary (line 557) | def _print_setup_summary(config: dict, hermes_home):
  function _prompt_container_resources (line 746) | def _prompt_container_resources(config: dict):
  function setup_model_provider (line 797) | def setup_model_provider(config: dict):
  function _check_espeak_ng (line 1792) | def _check_espeak_ng() -> bool:
  function _install_neutts_deps (line 1798) | def _install_neutts_deps() -> bool:
  function _setup_tts_provider (line 1848) | def _setup_tts_provider(config: dict):
  function setup_tts (line 1937) | def setup_tts(config: dict):
  function setup_terminal_backend (line 1947) | def setup_terminal_backend(config: dict):
  function setup_agent_settings (line 2253) | def setup_agent_settings(config: dict):
  function setup_gateway (line 2421) | def setup_gateway(config: dict):
  function setup_tools (line 2961) | def setup_tools(config: dict, first_install: bool = False):
  function _offer_openclaw_migration (line 2991) | def _offer_openclaw_migration(hermes_home: Path) -> bool:
  function run_setup_wizard (line 3098) | def run_setup_wizard(args):
  function _run_quick_setup (line 3296) | def _run_quick_setup(config: dict, hermes_home):

FILE: hermes_cli/skills_config.py
  function get_disabled_skills (line 31) | def get_disabled_skills(config: dict, platform: Optional[str] = None) ->...
  function save_disabled_skills (line 43) | def save_disabled_skills(config: dict, disabled: Set[str], platform: Opt...
  function _list_all_skills (line 56) | def _list_all_skills() -> List[dict]:
  function _get_categories (line 65) | def _get_categories(skills: List[dict]) -> List[str]:
  function _select_platform (line 72) | def _select_platform() -> Optional[str]:
  function _toggle_by_category (line 98) | def _toggle_by_category(skills: List[dict], disabled: Set[str]) -> Set[s...
  function skills_command (line 129) | def skills_command(args=None):

FILE: hermes_cli/skills_hub.py
  function _resolve_short_name (line 32) | def _resolve_short_name(name: str, sources, console: Console) -> str:
  function _format_extra_metadata_lines (line 79) | def _format_extra_metadata_lines(extra: Dict[str, Any]) -> list[str]:
  function _resolve_source_meta_and_bundle (line 107) | def _resolve_source_meta_and_bundle(identifier: str, sources):
  function _derive_category_from_install_path (line 137) | def _derive_category_from_install_path(install_path: str) -> str:
  function do_search (line 143) | def do_search(query: str, source: str = "all", limit: int = 10,
  function do_browse (line 182) | def do_browse(page: int = 1, page_size: int = 20, source: str = "all",
  function do_install (line 306) | def do_install(identifier: str, category: str = "", force: bool = False,
  function do_inspect (line 420) | def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
  function do_list (line 468) | def do_list(source_filter: str = "all", console: Optional[Console] = Non...
  function do_check (line 526) | def do_check(name: Optional[str] = None, console: Optional[Console] = No...
  function do_update (line 549) | def do_update(name: Optional[str] = None, console: Optional[Console] = N...
  function do_audit (line 569) | def do_audit(name: Optional[str] = None, console: Optional[Console] = No...
  function do_uninstall (line 602) | def do_uninstall(name: str, console: Optional[Console] = None,
  function do_tap (line 627) | def do_tap(action: str, repo: str = "", console: Optional[Console] = Non...
  function do_publish (line 669) | def do_publish(skill_path: str, target: str = "github", repo: str = "",
  function _github_publish (line 738) | def _github_publish(skill_path: Path, skill_name: str, target_repo: str,
  function do_snapshot_export (line 835) | def do_snapshot_export(output_path: str, console: Optional[Console] = No...
  function do_snapshot_import (line 870) | def do_snapshot_import(input_path: str, force: bool = False,
  function skills_command (line 921) | def skills_command(args) -> None:
  function handle_skills_slash (line 974) | def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> ...
  function _print_skills_help (line 1148) | def _print_skills_help(console: Console) -> None:

FILE: hermes_cli/skin_engine.py
  class SkinConfig (line 112) | class SkinConfig:
    method get_color (line 124) | def get_color(self, key: str, fallback: str = "") -> str:
    method get_spinner_list (line 128) | def get_spinner_list(self, key: str) -> List[str]:
    method get_spinner_wings (line 132) | def get_spinner_wings(self) -> List[Tuple[str, str]]:
    method get_branding (line 141) | def get_branding(self, key: str, fallback: str = "") -> str:
  function _skins_dir (line 514) | def _skins_dir() -> Path:
  function _load_skin_from_yaml (line 520) | def _load_skin_from_yaml(path: Path) -> Optional[Dict[str, Any]]:
  function _build_skin_config (line 533) | def _build_skin_config(data: Dict[str, Any]) -> SkinConfig:
  function list_skins (line 557) | def list_skins() -> List[Dict[str, str]]:
  function load_skin (line 588) | def load_skin(name: str) -> SkinConfig:
  function get_active_skin (line 607) | def get_active_skin() -> SkinConfig:
  function set_active_skin (line 615) | def set_active_skin(name: str) -> SkinConfig:
  function get_active_skin_name (line 623) | def get_active_skin_name() -> str:
  function init_skin_from_config (line 628) | def init_skin_from_config(config: dict) -> None:
  function get_active_prompt_symbol (line 646) | def get_active_prompt_symbol(fallback: str = "❯ ") -> str:
  function get_active_help_header (line 655) | def get_active_help_header(fallback: str = "(^_^)? Available Commands") ...
  function get_active_goodbye (line 664) | def get_active_goodbye(fallback: str = "Goodbye! ⚕") -> str:
  function get_prompt_toolkit_style_overrides (line 673) | def get_prompt_toolkit_style_overrides() -> Dict[str, str]:

FILE: hermes_cli/status.py
  function check_mark (line 21) | def check_mark(ok: bool) -> str:
  function redact_key (line 26) | def redact_key(key: str) -> str:
  function _format_iso_timestamp (line 35) | def _format_iso_timestamp(value) -> str:
  function _configured_model_label (line 54) | def _configured_model_label(config: dict) -> str:
  function _effective_provider_label (line 66) | def _effective_provider_label() -> str:
  function show_status (line 80) | def show_status(args):

FILE: hermes_cli/tools_config.py
  function _print_info (line 29) | def _print_info(text: str):
  function _print_success (line 32) | def _print_success(text: str):
  function _print_warning (line 35) | def _print_warning(text: str):
  function _print_error (line 38) | def _print_error(text: str):
  function _prompt (line 41) | def _prompt(question: str, default: str = None, password: bool = False) ...
  function _prompt_yes_no (line 57) | def _prompt_yes_no(question: str, default: bool = True) -> bool:
  function _run_post_setup (line 278) | def _run_post_setup(post_setup_key: str):
  function _get_enabled_platforms (line 329) | def _get_enabled_platforms() -> List[str]:
  function _platform_toolset_summary (line 343) | def _platform_toolset_summary(config: dict, platforms: Optional[List[str...
  function _get_platform_tools (line 359) | def _get_platform_tools(config: dict, platform: str) -> Set[str]:
  function _save_platform_tools (line 397) | def _save_platform_tools(config: dict, platform: str, enabled_toolset_ke...
  function _toolset_has_keys (line 438) | def _toolset_has_keys(ts_key: str) -> bool:
  function _prompt_choice (line 467) | def _prompt_choice(question: str, choices: list, default: int = 0) -> int:
  function _prompt_toolset_checklist (line 548) | def _prompt_toolset_checklist(platform_label: str, enabled: Set[str]) ->...
  function _configure_toolset (line 575) | def _configure_toolset(ts_key: str, config: dict):
  function _configure_tool_category (line 590) | def _configure_tool_category(ts_key: str, cat: dict, config: dict):
  function _is_provider_active (line 657) | def _is_provider_active(provider: dict, config: dict) -> bool:
  function _detect_active_provider_index (line 670) | def _detect_active_provider_index(providers: list, config: dict) -> int:
  function _configure_provider (line 682) | def _configure_provider(provider: dict, config: dict):
  function _configure_simple_requirements (line 742) | def _configure_simple_requirements(ts_key: str):
  function _reconfigure_tool (line 800) | def _reconfigure_tool(config: dict):
  function _configure_tool_category_for_reconfig (line 834) | def _configure_tool_category_for_reconfig(ts_key: str, cat: dict, config...
  function _reconfigure_provider (line 870) | def _reconfigure_provider(provider: dict, config: dict):
  function _reconfigure_simple_requirements (line 912) | def _reconfigure_simple_requirements(ts_key: str):
  function tools_command (line 938) | def tools_command(args=None, first_install: bool = False, config: dict =...
  function _configure_mcp_tools_interactive (line 1166) | def _configure_mcp_tools_interactive(config: dict):
  function _apply_toolset_change (line 1297) | def _apply_toolset_change(config: dict, platform: str, toolset_names: Li...
  function _apply_mcp_change (line 1307) | def _apply_mcp_change(config: dict, targets: List[str], action: str) -> ...
  function _print_tools_list (line 1332) | def _print_tools_list(enabled_toolsets: set, mcp_servers: dict, platform...
  function tools_disable_enable_command (line 1355) | def tools_disable_enable_command(args):

FILE: hermes_cli/uninstall.py
  function log_info (line 18) | def log_info(msg: str):
  function log_success (line 21) | def log_success(msg: str):
  function log_warn (line 24) | def log_warn(msg: str):
  function log_error (line 27) | def log_error(msg: str):
  function get_project_root (line 31) | def get_project_root() -> Path:
  function get_hermes_home (line 36) | def get_hermes_home() -> Path:
  function find_shell_configs (line 41) | def find_shell_configs() -> list:
  function remove_path_from_shell_configs (line 61) | def remove_path_from_shell_configs():
  function remove_wrapper_script (line 107) | def remove_wrapper_script():
  function uninstall_gateway_service (line 129) | def uninstall_gateway_service():
  function run_uninstall (line 179) | def run_uninstall(args):

FILE: hermes_state.py
  class SessionDB (line 107) | class SessionDB:
    method __init__ (line 115) | def __init__(self, db_path: Path = None):
    method _init_schema (line 131) | def _init_schema(self):
    method close (line 211) | def close(self):
    method create_session (line 222) | def create_session(
    method end_session (line 252) | def end_session(self, session_id: str, end_reason: str) -> None:
    method update_system_prompt (line 261) | def update_system_prompt(self, session_id: str, system_prompt: str) ->...
    method update_token_counts (line 270) | def update_token_counts(
    method get_session (line 331) | def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
    method resolve_session_id (line 340) | def resolve_session_id(self, session_id_or_prefix: str) -> Optional[str]:
    method sanitize_title (line 371) | def sanitize_title(title: Optional[str]) -> Optional[str]:
    method set_session_title (line 414) | def set_session_title(self, session_id: str, title: str) -> bool:
    method get_session_title (line 443) | def get_session_title(self, session_id: str) -> Optional[str]:
    method get_session_by_title (line 452) | def get_session_by_title(self, title: str) -> Optional[Dict[str, Any]]:
    method resolve_session_by_title (line 461) | def resolve_session_by_title(self, title: str) -> Optional[str]:
    method get_next_title_in_lineage (line 490) | def get_next_title_in_lineage(self, base_title: str) -> str:
    method list_sessions_rich (line 525) | def list_sessions_rich(
    method append_message (line 580) | def append_message(
    method get_messages (line 637) | def get_messages(self, session_id: str) -> List[Dict[str, Any]]:
    method get_messages_as_conversation (line 656) | def get_messages_as_conversation(self, session_id: str) -> List[Dict[s...
    method _sanitize_fts5_query (line 688) | def _sanitize_fts5_query(query: str) -> str:
    method search_messages (line 737) | def search_messages(
    method search_sessions (line 832) | def search_sessions(
    method session_count (line 856) | def session_count(self, source: str = None) -> int:
    method message_count (line 866) | def message_count(self, session_id: str = None) -> int:
    method export_session (line 880) | def export_session(self, session_id: str) -> Optional[Dict[str, Any]]:
    method export_all (line 888) | def export_all(self, source: str = None) -> List[Dict[str, Any]]:
    method clear_messages (line 900) | def clear_messages(self, session_id: str) -> None:
    method delete_session (line 912) | def delete_session(self, session_id: str) -> bool:
    method prune_sessions (line 925) | def prune_sessions(self, older_than_days: int = 90, source: str = None...

FILE: hermes_time.py
  function _resolve_timezone_name (line 37) | def _resolve_timezone_name() -> str:
  function _get_zoneinfo (line 65) | def _get_zoneinfo(name: str) -> Optional[ZoneInfo]:
  function get_timezone (line 79) | def get_timezone() -> Optional[ZoneInfo]:
  function get_timezone_name (line 92) | def get_timezone_name() -> str:
  function now (line 100) | def now() -> datetime:
  function reset_cache (line 114) | def reset_cache() -> None:

FILE: honcho_integration/cli.py
  function _read_config (line 17) | def _read_config() -> dict:
  function _write_config (line 26) | def _write_config(cfg: dict) -> None:
  function _resolve_api_key (line 34) | def _resolve_api_key(cfg: dict) -> str:
  function _prompt (line 40) | def _prompt(label: str, default: str | None = None, secret: bool = False...
  function _ensure_sdk_installed (line 56) | def _ensure_sdk_installed() -> bool:
  function cmd_setup (line 86) | def cmd_setup(args) -> None:
  function cmd_status (line 216) | def cmd_status(args) -> None:
  function cmd_sessions (line 270) | def cmd_sessions(args) -> None:
  function cmd_map (line 289) | def cmd_map(args) -> None:
  function cmd_peer (line 314) | def cmd_peer(args) -> None:
  function cmd_mode (line 367) | def cmd_mode(args) -> None:
  function cmd_tokens (line 398) | def cmd_tokens(args) -> None:
  function cmd_identity (line 440) | def cmd_identity(args) -> None:
  function cmd_migrate (line 514) | def cmd_migrate(args) -> None:
  function honcho_command (line 742) | def honcho_command(args) -> None:

FILE: honcho_integration/client.py
  function _normalize_recall_mode (line 34) | def _normalize_recall_mode(val: str) -> str:
  function _resolve_memory_mode (line 40) | def _resolve_memory_mode(
  class HonchoClientConfig (line 65) | class HonchoClientConfig:
    method peer_memory_mode (line 87) | def peer_memory_mode(self, peer_name: str) -> str:
    method from_env (line 117) | def from_env(cls, workspace_id: str = "hermes") -> HonchoClientConfig:
    method from_global_config (line 130) | def from_global_config(
    method _git_repo_name (line 259) | def _git_repo_name(cwd: str) -> str | None:
    method resolve_session_name (line 274) | def resolve_session_name(
    method get_linked_workspaces (line 331) | def get_linked_workspaces(self) -> list[str]:
  function get_honcho_client (line 346) | def get_honcho_client(config: HonchoClientConfig | None = None) -> Honcho:
  function reset_honcho_client (line 408) | def reset_honcho_client() -> None:

FILE: honcho_integration/session.py
  class HonchoSession (line 25) | class HonchoSession:
    method add_message (line 42) | def add_message(self, role: str, content: str, **kwargs: Any) -> None:
    method get_history (line 53) | def get_history(self, max_messages: int = 50) -> list[dict[str, Any]]:
    method clear (line 62) | def clear(self) -> None:
  class HonchoSessionManager (line 68) | class HonchoSessionManager:
    method __init__ (line 76) | def __init__(
    method honcho (line 127) | def honcho(self) -> Honcho:
    method _get_or_create_peer (line 133) | def _get_or_create_peer(self, peer_id: str) -> Any:
    method _get_or_create_honcho_session (line 147) | def _get_or_create_honcho_session(
    method _sanitize_id (line 206) | def _sanitize_id(self, id_str: str) -> str:
    method get_or_create (line 210) | def get_or_create(self, key: str) -> HonchoSession:
    method _flush_session (line 273) | def _flush_session(self, session: HonchoSession) -> bool:
    method _async_writer_loop (line 310) | def _async_writer_loop(self) -> None:
    method save (line 349) | def save(self, session: HonchoSession) -> None:
    method flush_all (line 373) | def flush_all(self) -> None:
    method shutdown (line 395) | def shutdown(self) -> None:
    method delete (line 402) | def delete(self, key: str) -> bool:
    method new_session (line 409) | def new_session(self, key: str) -> HonchoSession:
    method _dynamic_reasoning_level (line 438) | def _dynamic_reasoning_level(self, query: str) -> str:
    method dialectic_query (line 464) | def dialectic_query(
    method prefetch_dialectic (line 504) | def prefetch_dialectic(self, session_key: str, query: str) -> None:
    method set_dialectic_result (line 524) | def set_dialectic_result(self, session_key: str, result: str) -> None:
    method pop_dialectic_result (line 531) | def pop_dialectic_result(self, session_key: str) -> str:
    method prefetch_context (line 540) | def prefetch_context(self, session_key: str, user_message: str | None ...
    method set_context_result (line 555) | def set_context_result(self, session_key: str, result: dict[str, str])...
    method pop_context_result (line 562) | def pop_context_result(self, session_key: str) -> dict[str, str]:
    method get_prefetch_context (line 571) | def get_prefetch_context(self, session_key: str, user_message: str | N...
    method migrate_local_history (line 626) | def migrate_local_history(self, session_key: str, messages: list[dict[...
    method _format_migration_transcript (line 668) | def _format_migration_transcript(session_key: str, messages: list[dict...
    method migrate_memory_files (line 698) | def migrate_memory_files(self, session_key: str, memory_dir: str) -> b...
    method get_peer_card (line 797) | def get_peer_card(self, session_key: str) -> list[str]:
    method search_context (line 826) | def search_context(self, session_key: str, query: str, max_tokens: int...
    method create_conclusion (line 870) | def create_conclusion(self, session_key: str, content: str) -> bool:
    method seed_ai_identity (line 905) | def seed_ai_identity(self, session_key: str, content: str, source: str...
    method get_ai_representation (line 950) | def get_ai_representation(self, session_key: str) -> dict[str, str]:
    method list_sessions (line 981) | def list_sessions(self) -> list[dict[str, Any]]:

FILE: landingpage/script.js
  constant PLATFORMS (line 6) | const PLATFORMS = {
  function detectPlatform (line 17) | function detectPlatform() {
  function switchPlatform (line 21) | function switchPlatform(platform) {
  function switchStepPlatform (line 43) | function switchStepPlatform(platform) {
  function toggleMobileNav (line 61) | function toggleMobileNav() {
  function toggleSpecs (line 66) | function toggleSpecs() {
  function copyInstall (line 98) | function copyInstall() {
  function copyText (line 112) | function copyText(btn) {
  function initScrollAnimations (line 126) | function initScrollAnimations() {
  constant CURSOR (line 161) | const CURSOR = '<span class="terminal-cursor">█</span>';
  class TerminalDemo (line 314) | class TerminalDemo {
    method constructor (line 315) | constructor(container) {
    method start (line 321) | async start() {
    method stop (line 335) | stop() {
    method execute (line 339) | async execute(step) {
    method append (line 367) | append(html) {
    method render (line 372) | render() {
    method clear (line 377) | clear() {
    method sleep (line 382) | sleep(ms) {
  function initNoiseOverlay (line 388) | function initNoiseOverlay() {

FILE: mini_swe_runner.py
  function create_environment (line 105) | def create_environment(
  class MiniSWERunner (line 145) | class MiniSWERunner:
    method __init__ (line 151) | def __init__(
    method _create_env (line 231) | def _create_env(self):
    method _cleanup_env (line 242) | def _cleanup_env(self):
    method _execute_command (line 251) | def _execute_command(self, command: str, timeout: int = None) -> Dict[...
    method _format_tools_for_system_message (line 279) | def _format_tools_for_system_message(self) -> str:
    method _convert_to_hermes_format (line 292) | def _convert_to_hermes_format(
    method run_task (line 401) | def run_task(self, task: str) -> Dict[str, Any]:
    method run_batch (line 560) | def run_batch(
  function main (line 617) | def main(

FILE: minisweagent_path.py
  function _read_gitdir (line 20) | def _read_gitdir(repo_root: Path) -> Optional[Path]:
  function discover_minisweagent_src (line 44) | def discover_minisweagent_src(repo_root: Optional[Path] = None) -> Optio...
  function ensure_minisweagent_on_path (line 76) | def ensure_minisweagent_on_path(repo_root: Optional[Path] = None) -> Opt...

FILE: model_tools.py
  function _get_tool_loop (line 45) | def _get_tool_loop():
  function _get_worker_loop (line 60) | def _get_worker_loop():
  function _run_async (line 82) | def _run_async(coro):
  function _discover_tools (line 133) | def _discover_tools():
  function get_tool_definitions (line 235) | def get_tool_definitions(
  function handle_function_call (line 373) | def handle_function_call(
  function get_all_tool_names (line 455) | def get_all_tool_names() -> List[str]:
  function get_toolset_for_tool (line 460) | def get_toolset_for_tool(tool_name: str) -> Optional[str]:
  function get_available_toolsets (line 465) | def get_available_toolsets() -> Dict[str, dict]:
  function check_toolset_requirements (line 470) | def check_toolset_requirements() -> Dict[str, bool]:
  function check_tool_availability (line 475) | def check_tool_availability(quiet: bool = False) -> Tuple[List[str], Lis...

FILE: optional-skills/blockchain/base/scripts/base_client.py
  function _http_get_json (line 80) | def _http_get_json(url: str, timeout: int = 10, retries: int = 2) -> Any:
  function _rpc_call (line 99) | def _rpc_call(method: str, params: list = None, retries: int = 2) -> Any:
  function _rpc_batch_chunk (line 140) | def _rpc_batch_chunk(items: list) -> list:
  function rpc_batch (line 166) | def rpc_batch(calls: list) -> list:
  function wei_to_eth (line 184) | def wei_to_eth(wei: int) -> float:
  function wei_to_gwei (line 188) | def wei_to_gwei(wei: int) -> float:
  function hex_to_int (line 192) | def hex_to_int(hex_str: Optional[str]) -> int:
  function print_json (line 199) | def print_json(obj: Any) -> None:
  function _short_addr (line 203) | def _short_addr(addr: str) -> str:
  function _encode_address (line 214) | def _encode_address(addr: str) -> str:
  function _decode_uint (line 220) | def _decode_uint(hex_data: Optional[str]) -> int:
  function _decode_string (line 227) | def _decode_string(hex_data: Optional[str]) -> str:
  function _eth_call (line 242) | def _eth_call(to: str, selector: str, args: str = "", block: str = "late...
  function fetch_prices (line 268) | def fetch_prices(addresses: List[str], max_lookups: int = 20) -> Dict[st...
  function fetch_eth_price (line 293) | def fetch_eth_price() -> Optional[float]:
  function resolve_token_name (line 303) | def resolve_token_name(addr: str) -> Optional[Dict[str, str]]:
  function _token_label (line 322) | def _token_label(addr: str) -> str:
  function cmd_stats (line 334) | def cmd_stats(_args):
  function cmd_wallet (line 382) | def cmd_wallet(args):
  function cmd_tx (line 498) | def cmd_tx(args):
  function cmd_token (line 603) | def cmd_token(args):
  function cmd_gas (line 663) | def cmd_gas(_args):
  function cmd_contract (line 757) | def cmd_contract(args):
  function cmd_whales (line 872) | def cmd_whales(args):
  function cmd_price (line 915) | def cmd_price(args):
  function main (line 956) | def main():

FILE: optional-skills/blockchain/solana/scripts/solana_client.py
  function _http_get_json (line 74) | def _http_get_json(url: str, timeout: int = 10, retries: int = 2) -> Any:
  function _rpc_call (line 93) | def _rpc_call(method: str, params: list = None, retries: int = 2) -> Any:
  function rpc_batch (line 131) | def rpc_batch(calls: list) -> list:
  function lamports_to_sol (line 156) | def lamports_to_sol(lamports: int) -> float:
  function print_json (line 160) | def print_json(obj: Any) -> None:
  function _short_mint (line 164) | def _short_mint(mint: str) -> str:
  function fetch_prices (line 175) | def fetch_prices(mints: List[str], max_lookups: int = 20) -> Dict[str, f...
  function fetch_sol_price (line 200) | def fetch_sol_price() -> Optional[float]:
  function resolve_token_name (line 210) | def resolve_token_name(mint: str) -> Optional[Dict[str, str]]:
  function _token_label (line 225) | def _token_label(mint: str) -> str:
  function cmd_stats (line 236) | def cmd_stats(_args):
  function cmd_wallet (line 284) | def cmd_wallet(args):
  function cmd_tx (line 396) | def cmd_tx(args):
  function cmd_token (line 451) | def cmd_token(args):
  function cmd_activity (line 499) | def cmd_activity(args):
  function cmd_nft (line 521) | def cmd_nft(args):
  function cmd_whales (line 548) | def cmd_whales(args):
  function cmd_price (line 614) | def cmd_price(args):
  function main (line 643) | def main():

FILE: optional-skills/mcp/fastmcp/scripts/scaffold_fastmcp.py
  function list_templates (line 16) | def list_templates() -> list[str]:
  function render_template (line 20) | def render_template(template_name: str, server_name: str) -> str:
  function main (line 28) | def main() -> int:

FILE: optional-skills/mcp/fastmcp/templates/api_wrapper.py
  function _headers (line 17) | def _headers() -> dict[str, str]:
  function _request (line 24) | def _request(method: str, path: str, *, params: dict[str, Any] | None = ...
  function health_check (line 33) | def health_check() -> dict[str, Any]:
  function get_resource (line 40) | def get_resource(resource_id: str) -> dict[str, Any]:
  function search_resources (line 47) | def search_resources(query: str, limit: int = 10) -> dict[str, Any]:

FILE: optional-skills/mcp/fastmcp/templates/database_server.py
  function _connect (line 18) | def _connect() -> sqlite3.Connection:
  function _reject_mutation (line 22) | def _reject_mutation(sql: str) -> None:
  function _validate_table_name (line 28) | def _validate_table_name(table_name: str) -> str:
  function list_tables (line 35) | def list_tables() -> list[str]:
  function describe_table (line 45) | def describe_table(table_name: str) -> list[dict[str, Any]]:
  function query (line 64) | def query(sql: str, limit: int = 50) -> dict[str, Any]:

FILE: optional-skills/mcp/fastmcp/templates/file_processor.py
  function _read_text (line 12) | def _read_text(path: str) -> str:
  function summarize_text_file (line 23) | def summarize_text_file(path: str, preview_chars: int = 1200) -> dict[st...
  function search_text_file (line 36) | def search_text_file(path: str, needle: str, max_matches: int = 20) -> d...
  function read_file_resource (line 49) | def read_file_resource(path: str) -> str:

FILE: optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py
  class ItemResult (line 148) | class ItemResult:
  function parse_selection_values (line 157) | def parse_selection_values(values: Optional[Sequence[str]]) -> List[str]:
  function resolve_selected_options (line 167) | def resolve_selected_options(
  function sha256_file (line 207) | def sha256_file(path: Path) -> str:
  function read_text (line 215) | def read_text(path: Path) -> str:
  function normalize_text (line 219) | def normalize_text(text: str) -> str:
  function ensure_parent (line 223) | def ensure_parent(path: Path) -> None:
  function load_yaml_file (line 227) | def load_yaml_file(path: Path) -> Dict[str, Any]:
  function dump_yaml_file (line 234) | def dump_yaml_file(path: Path, data: Dict[str, Any]) -> None:
  function parse_env_file (line 244) | def parse_env_file(path: Path) -> Dict[str, str]:
  function save_env_file (line 257) | def save_env_file(path: Path, data: Dict[str, str]) -> None:
  function backup_existing (line 263) | def backup_existing(path: Path, backup_root: Path) -> Optional[Path]:
  function parse_existing_memory_entries (line 276) | def parse_existing_memory_entries(path: Path) -> List[str]:
  function extract_markdown_entries (line 287) | def extract_markdown_entries(text: str) -> List[str]:
  function merge_entries (line 363) | def merge_entries(
  function relative_label (line 397) | def relative_label(path: Path, root: Path) -> str:
  function write_report (line 404) | def write_report(output_dir: Path, report: Dict[str, Any]) -> None:
  class Migrator (line 444) | class Migrator:
    method __init__ (line 445) | def __init__(
    method is_selected (line 489) | def is_selected(self, option_id: str) -> bool:
    method record (line 492) | def record(
    method source_candidate (line 512) | def source_candidate(self, *relative_paths: str) -> Optional[Path]:
    method resolve_skill_destination (line 519) | def resolve_skill_destination(self, destination: Path) -> Path:
    method migrate (line 531) | def migrate(self) -> Dict[str, Any]:
    method run_if_selected (line 583) | def run_if_selected(self, option_id: str, func) -> None:
    method build_report (line 590) | def build_report(self) -> Dict[str, Any]:
    method maybe_backup (line 633) | def maybe_backup(self, path: Path) -> Optional[Path]:
    method write_overflow_entries (line 638) | def write_overflow_entries(self, kind: str, entries: Sequence[str]) ->...
    method copy_file (line 647) | def copy_file(self, source: Path, destination: Path, kind: str) -> None:
    method migrate_soul (line 667) | def migrate_soul(self) -> None:
    method migrate_workspace_agents (line 674) | def migrate_workspace_agents(self) -> None:
    method migrate_memory (line 688) | def migrate_memory(self, source: Optional[Path], destination: Path, li...
    method migrate_command_allowlist (line 731) | def migrate_command_allowlist(self) -> None:
    method load_openclaw_config (line 790) | def load_openclaw_config(self) -> Dict[str, Any]:
    method merge_env_values (line 800) | def merge_env_values(self, additions: Dict[str, str], kind: str, sourc...
    method migrate_messaging_settings (line 846) | def migrate_messaging_settings(self, config: Optional[Dict[str, Any]] ...
    method handle_secret_settings (line 876) | def handle_secret_settings(self, config: Optional[Dict[str, Any]] = No...
    method migrate_secret_settings (line 902) | def migrate_secret_settings(self, config: Dict[str, Any]) -> None:
    method migrate_discord_settings (line 925) | def migrate_discord_settings(self, config: Optional[Dict[str, Any]] = ...
    method migrate_slack_settings (line 943) | def migrate_slack_settings(self, config: Optional[Dict[str, Any]] = No...
    method migrate_whatsapp_settings (line 964) | def migrate_whatsapp_settings(self, config: Optional[Dict[str, Any]] =...
    method migrate_signal_settings (line 979) | def migrate_signal_settings(self, config: Optional[Dict[str, Any]] = N...
    method handle_provider_keys (line 1000) | def handle_provider_keys(self, config: Optional[Dict[str, Any]] = None...
    method migrate_provider_keys (line 1015) | def migrate_provider_keys(self, config: Dict[str, Any]) -> None:
    method migrate_model_config (line 1083) | def migrate_model_config(self, config: Optional[Dict[str, Any]] = None...
    method migrate_tts_config (line 1125) | def migrate_tts_config(self, config: Optional[Dict[str, Any]] = None) ...
    method migrate_shared_skills (line 1198) | def migrate_shared_skills(self) -> None:
    method migrate_daily_memory (line 1252) | def migrate_daily_memory(self) -> None:
    method migrate_skills (line 1307) | def migrate_skills(self) -> None:
    method copy_tree_non_destructive (line 1361) | def copy_tree_non_destructive(
    method archive_docs (line 1414) | def archive_docs(self) -> None:
    method archive_path (line 1450) | def archive_path(self, source: Path, reason: str) -> None:
  function parse_args (line 1463) | def parse_args() -> argparse.Namespace:
  function main (line 1507) | def main() -> int:

FILE: optional-skills/productivity/telephony/scripts/telephony.py
  class TelephonyError (line 59) | class TelephonyError(RuntimeError):
  class OwnedTwilioNumber (line 64) | class OwnedTwilioNumber:
  function _hermes_home (line 71) | def _hermes_home() -> Path:
  function _env_path (line 75) | def _env_path() -> Path:
  function _config_path (line 79) | def _config_path() -> Path:
  function _state_path (line 83) | def _state_path() -> Path:
  function _load_root_config (line 87) | def _load_root_config() -> dict[str, Any]:
  function _config_lookup (line 103) | def _config_lookup(*paths: tuple[str, ...], default: str = "") -> str:
  function _load_dotenv_values (line 117) | def _load_dotenv_values(path: Path | None = None) -> dict[str, str]:
  function _env_or_config (line 135) | def _env_or_config(env_key: str, *config_paths: tuple[str, ...], default...
  function _load_state (line 145) | def _load_state(path: Path | None = None) -> dict[str, Any]:
  function _save_state (line 159) | def _save_state(state: dict[str, Any], path: Path | None = None) -> Path:
  function _quote_env_value (line 166) | def _quote_env_value(value: str) -> str:
  function _upsert_env_file (line 173) | def _upsert_env_file(updates: dict[str, str], env_path: Path | None = No...
  function _normalize_phone (line 206) | def _normalize_phone(number: str) -> str:
  function _mask_phone (line 220) | def _mask_phone(number: str) -> str:
  function _parse_twilio_date (line 227) | def _parse_twilio_date(value: str | None) -> datetime | None:
  function _json_request (line 237) | def _json_request(
  function _twilio_creds (line 275) | def _twilio_creds() -> tuple[str, str]:
  function _twilio_basic_headers (line 294) | def _twilio_basic_headers() -> dict[str, str]:
  function _twilio_request (line 300) | def _twilio_request(method: str, path: str, *, params=None, form=None) -...
  function _twilio_owned_numbers (line 311) | def _twilio_owned_numbers(limit: int = 50) -> list[OwnedTwilioNumber]:
  function _remember_twilio_number (line 330) | def _remember_twilio_number(
  function _remember_vapi_number (line 359) | def _remember_vapi_number(
  function _resolve_twilio_number (line 382) | def _resolve_twilio_number(identifier: str | None = None) -> OwnedTwilio...
  function _vapi_api_key (line 427) | def _vapi_api_key() -> str:
  function _vapi_phone_number_id (line 435) | def _vapi_phone_number_id() -> str:
  function _bland_api_key (line 446) | def _bland_api_key() -> str:
  function _ai_provider (line 454) | def _ai_provider(default: str = DEFAULT_AI_PROVIDER) -> str:
  function _twilio_search_numbers (line 463) | def _twilio_search_numbers(
  function _twilio_buy_number (line 512) | def _twilio_buy_number(
  function _twilio_list_owned (line 542) | def _twilio_list_owned() -> dict[str, Any]:
  function _twilio_set_default (line 560) | def _twilio_set_default(identifier: str, *, save_env: bool = False) -> d...
  function _twiml_say (line 579) | def _twiml_say(message: str, voice: str) -> str:
  function _twiml_play (line 583) | def _twiml_play(audio_url: str) -> str:
  function _twilio_call (line 587) | def _twilio_call(
  function _twilio_call_status (line 627) | def _twilio_call_status(call_sid: str) -> dict[str, Any]:
  function _twilio_send_sms (line 644) | def _twilio_send_sms(
  function _checkpoint_for_messages (line 675) | def _checkpoint_for_messages(messages: list[dict[str, Any]]) -> tuple[st...
  function _messages_after_checkpoint (line 682) | def _messages_after_checkpoint(messages: list[dict[str, Any]], last_sid:...
  function _twilio_inbox (line 693) | def _twilio_inbox(
  function _vapi_import_twilio_number (line 749) | def _vapi_import_twilio_number(
  function _bland_call (line 795) | def _bland_call(
  function _bland_status (line 845) | def _bland_status(call_id: str, analyze: str | None = None) -> dict[str,...
  function _vapi_call (line 873) | def _vapi_call(
  function _vapi_status (line 948) | def _vapi_status(call_id: str) -> dict[str, Any]:
  function _provider_decision_tree (line 971) | def _provider_decision_tree() -> list[dict[str, str]]:
  function diagnose (line 996) | def diagnose() -> dict[str, Any]:
  function save_twilio (line 1079) | def save_twilio(account_sid: str, auth_token: str, phone_number: str = "...
  function save_bland (line 1101) | def save_bland(api_key: str, voice: str = BLAND_DEFAULT_VOICE) -> dict[s...
  function save_vapi (line 1118) | def save_vapi(
  function _build_parser (line 1148) | def _build_parser() -> argparse.ArgumentParser:
  function _dispatch (line 1233) | def _dispatch(args: argparse.Namespace) -> dict[str, Any]:
  function main (line 1330) | def main(argv: list[str] | None = None) -> int:

FILE: optional-skills/security/oss-forensics/scripts/evidence-store.py
  function _now_iso (line 51) | def _now_iso():
  function _sha256 (line 55) | def _sha256(content: str) -> str:
  class EvidenceStore (line 59) | class EvidenceStore:
    method __init__ (line 60) | def __init__(self, filepath: str):
    method _save (line 82) | def _save(self):
    method _next_id (line 87) | def _next_id(self) -> str:
    method add (line 90) | def add(
    method list_evidence (line 127) | def list_evidence(self, filter_type: str = None, filter_actor: str = N...
    method verify_integrity (line 135) | def verify_integrity(self):
    method query (line 149) | def query(self, keyword: str):
    method export_markdown (line 160) | def export_markdown(self) -> str:
    method summary (line 191) | def summary(self) -> dict:
  function main (line 210) | def main():

FILE: rl_cli.py
  function load_hermes_config (line 70) | def load_hermes_config() -> dict:
  function check_requirements (line 181) | def check_requirements():
  function check_tinker_atropos (line 203) | def check_tinker_atropos():
  function list_environments_sync (line 220) | def list_environments_sync():
  function main (line 236) | def main(

FILE: run_agent.py
  class _SafeWriter (line 110) | class _SafeWriter:
    method __init__ (line 126) | def __init__(self, inner):
    method write (line 129) | def write(self, data):
    method flush (line 135) | def flush(self):
    method fileno (line 141) | def fileno(self):
    method isatty (line 144) | def isatty(self):
    method __getattr__ (line 150) | def __getattr__(self, name):
  function _install_safe_stdio (line 154) | def _install_safe_stdio() -> None:
  class IterationBudget (line 162) | class IterationBudget:
    method __init__ (line 173) | def __init__(self, max_total: int):
    method consume (line 178) | def consume(self) -> bool:
    method refund (line 186) | def refund(self) -> None:
    method used (line 193) | def used(self) -> int:
    method remaining (line 197) | def remaining(self) -> int:
  function _is_destructive_command (line 247) | def _is_destructive_command(cmd: str) -> bool:
  function _should_parallelize_tool_batch (line 258) | def _should_parallelize_tool_batch(tool_calls) -> bool:
  function _extract_parallel_scope_path (line 302) | def _extract_parallel_scope_path(tool_name: str, function_args: dict) ->...
  function _paths_overlap (line 315) | def _paths_overlap(left: Path, right: Path) -> bool:
  function _inject_honcho_turn_context (line 326) | def _inject_honcho_turn_context(content, turn_context: str):
  class AIAgent (line 352) | class AIAgent:
    method base_url (line 361) | def base_url(self) -> str:
    method base_url (line 365) | def base_url(self, value: str) -> None:
    method __init__ (line 369) | def __init__(
    method reset_session_state (line 1060) | def reset_session_state(self):
    method _safe_print (line 1101) | def _safe_print(*args, **kwargs):
    method _vprint (line 1113) | def _vprint(self, *args, force: bool = False, **kwargs):
    method _is_direct_openai_url (line 1127) | def _is_direct_openai_url(self, base_url: str = None) -> bool:
    method _max_tokens_param (line 1132) | def _max_tokens_param(self, value: int) -> dict:
    method _has_content_after_think_block (line 1143) | def _has_content_after_think_block(self, content: str) -> bool:
    method _strip_think_blocks (line 1166) | def _strip_think_blocks(self, content: str) -> str:
    method _looks_like_codex_intermediate_ack (line 1178) | def _looks_like_codex_intermediate_ack(
    method _extract_reasoning (line 1250) | def _extract_reasoning(self, assistant_message) -> Optional[str]:
    method _cleanup_task_resources (line 1293) | def _cleanup_task_resources(self, task_id: str) -> None:
    method _spawn_background_review (line 1345) | def _spawn_background_review(
    method _apply_persist_user_message_override (line 1398) | def _apply_persist_user_message_override(self, messages: List[Dict]) -...
    method _persist_session (line 1416) | def _persist_session(self, messages: List[Dict], conversation_history:...
    method _flush_messages_to_session_db (line 1426) | def _flush_messages_to_session_db(self, messages: List[Dict], conversa...
    method _get_messages_up_to_last_assistant (line 1463) | def _get_messages_up_to_last_assistant(self, messages: List[Dict]) -> ...
    method _format_tools_for_system_message (line 1494) | def _format_tools_for_system_message(self) -> str:
    method _convert_to_trajectory_format (line 1518) | def _convert_to_trajectory_format(self, messages: List[Dict[str, Any]]...
    method _save_trajectory (line 1682) | def _save_trajectory(self, messages: List[Dict[str, Any]], user_query:...
    method _mask_api_key_for_logs (line 1697) | def _mask_api_key_for_logs(self, key: Optional[str]) -> Optional[str]:
    method _dump_api_request_debug (line 1704) | def _dump_api_request_debug(
    method _clean_session_content (line 1787) | def _clean_session_content(content: str) -> str:
    method _save_session_log (line 1796) | def _save_session_log(self, messages: List[Dict[str, Any]] = None):
    method interrupt (line 1845) | def interrupt(self, message: str = None) -> None:
    method clear_interrupt (line 1884) | def clear_interrupt(self) -> None:
    method _hydrate_todo_store (line 1890) | def _hydrate_todo_store(self, history: List[Dict[str, Any]]) -> None:
    method is_interrupted (line 1923) | def is_interrupted(self) -> bool:
    method _honcho_should_activate (line 1929) | def _honcho_should_activate(self, hcfg) -> bool:
    method _strip_honcho_tools_from_surface (line 1935) | def _strip_honcho_tools_from_surface(self) -> None:
    method _activate_honcho (line 1949) | def _activate_honcho(
    method _register_honcho_exit_hook (line 2034) | def _register_honcho_exit_hook(self) -> None:
    method _queue_honcho_prefetch (line 2053) | def _queue_honcho_prefetch(self, user_message: str) -> None:
    method _honcho_prefetch (line 2068) | def _honcho_prefetch(self, user_message: str) -> str:
    method _honcho_save_user_observation (line 2107) | def _honcho_save_user_observation(self, content: str) -> str:
    method _honcho_sync (line 2128) | def _honcho_sync(self, user_content: str, assistant_content: str) -> N...
    method _build_system_prompt (line 2144) | def _build_system_prompt(self, system_message: str = None) -> str:
    method _get_tool_call_id_static (line 2306) | def _get_tool_call_id_static(tc) -> str:
    method _sanitize_api_messages (line 2313) | def _sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dic...
    method _cap_delegate_task_calls (line 2371) | def _cap_delegate_task_calls(tool_calls: list) -> list:
    method _deduplicate_tool_calls (line 2401) | def _deduplicate_tool_calls(tool_calls: list) -> list:
    method _repair_tool_call (line 2418) | def _repair_tool_call(self, tool_name: str) -> str | None:
    method _invalidate_system_prompt (line 2446) | def _invalidate_system_prompt(self):
    method _responses_tools (line 2457) | def _responses_tools(self, tools: Optional[List[Dict[str, Any]]] = Non...
    method _split_responses_tool_id (line 2479) | def _split_responses_tool_id(raw_id: Any) -> tuple[Optional[str], Opti...
    method _derive_responses_function_call_id (line 2495) | def _derive_responses_function_call_id(
    method _chat_messages_to_responses_input (line 2524) | def _chat_messages_to_responses_input(self, messages: List[Dict[str, A...
    method _preflight_codex_input_items (line 2621) | def _preflight_codex_input_items(self, raw_items: Any) -> List[Dict[st...
    method _preflight_codex_api_kwargs (line 2707) | def _preflight_codex_api_kwargs(
    method _extract_responses_message_text (line 2831) | def _extract_responses_message_text(self, item: Any) -> str:
    method _extract_responses_reasoning_text (line 2847) | def _extract_responses_reasoning_text(self, item: Any) -> str:
    method _normalize_codex_response (line 2863) | def _normalize_codex_response(self, response: Any) -> tuple[Any, str]:
    method _thread_identity (line 3012) | def _thread_identity(self) -> str:
    method _client_log_context (line 3016) | def _client_log_context(self) -> str:
    method _openai_client_lock (line 3025) | def _openai_client_lock(self) -> threading.RLock:
    method _is_openai_client_closed (line 3033) | def _is_openai_client_closed(client: Any) -> bool:
    method _create_openai_client (line 3043) | def _create_openai_client(self, client_kwargs: dict, *, reason: str, s...
    method _close_openai_client (line 3064) | def _close_openai_client(self, client: Any, *, reason: str, shared: bo...
    method _replace_primary_openai_client (line 3084) | def _replace_primary_openai_client(self, *, reason: str) -> bool:
    method _ensure_primary_openai_client (line 3101) | def _ensure_primary_openai_client(self, *, reason: str) -> Any:
    method _create_request_openai_client (line 3117) | def _create_request_openai_client(self, *, reason: str) -> Any:
    method _close_request_openai_client (line 3127) | def _close_request_openai_client(self, client: Any, *, reason: str) ->...
    method _run_codex_stream (line 3130) | def _run_codex_stream(self, api_kwargs: dict, client: Any = None, on_f...
    method _run_codex_create_stream_fallback (line 3183) | def _run_codex_create_stream_fallback(self, api_kwargs: dict, client: ...
    method _try_refresh_codex_client_credentials (line 3223) | def _try_refresh_codex_client_credentials(self, *, force: bool = True)...
    method _try_refresh_nous_client_credentials (line 3252) | def _try_refresh_nous_client_credentials(self, *, force: bool = True) ...
    method _try_refresh_anthropic_client_credentials (line 3287) | def _try_refresh_anthropic_client_credentials(self) -> bool:
    method _anthropic_messages_create (line 3322) | def _anthropic_messages_create(self, api_kwargs: dict):
    method _interruptible_api_call (line 3327) | def _interruptible_api_call(self, api_kwargs: dict):
    method _fire_stream_delta (line 3390) | def _fire_stream_delta(self, text: str) -> None:
    method _fire_reasoning_delta (line 3399) | def _fire_reasoning_delta(self, text: str) -> None:
    method _has_stream_consumers (line 3408) | def _has_stream_consumers(self) -> bool:
    method _interruptible_streaming_api_call (line 3415) | def _interruptible_streaming_api_call(
    method _try_activate_fallback (line 3660) | def _try_activate_fallback(self) -> bool:
    method _content_has_image_parts (line 3752) | def _content_has_image_parts(content: Any) -> bool:
    method _materialize_data_url_for_vision (line 3761) | def _materialize_data_url_for_vision(image_url: str) -> tuple[str, Opt...
    method _describe_image_for_anthropic_fallback (line 3781) | def _describe_image_for_anthropic_fallback(self, image_url: str, role:...
    method _preprocess_anthropic_content (line 3832) | def _preprocess_anthropic_content(self, content: Any, role: str) -> Any:
    method _prepare_anthropic_messages_for_api (line 3876) | def _prepare_anthropic_messages_for_api(self, api_messages: list) -> l...
    method _build_api_kwargs (line 3893) | def _build_api_kwargs(self, api_messages: list) -> dict:
    method _supports_reasoning_extra_body (line 4067) | def _supports_reasoning_extra_body(self) -> bool:
    method _github_models_reasoning_extra_body (line 4101) | def _github_models_reasoning_extra_body(self) -> dict | None:
    method _build_assistant_message (line 4133) | def _build_assistant_message(self, assistant_message, finish_reason: s...
    method _sanitize_tool_calls_for_strict_api (line 4239) | def _sanitize_tool_calls_for_strict_api(api_msg: dict) -> dict:
    method flush_memories (line 4263) | def flush_memories(self, messages: list = None, min_turns: int = None):
    method _compress_context (line 4425) | def _compress_context(self, messages: list, system_message: str, *, ap...
    method _execute_tool_calls (line 4476) | def _execute_tool_calls(self, assistant_message, messages: list, effec...
    method _invoke_tool (line 4499) | def _invoke_tool(self, function_name: str, function_args: dict, effect...
    method _execute_tool_calls_concurrent (line 4563) | def _execute_tool_calls_concurrent(self, assistant_message, messages: ...
    method _execute_tool_calls_sequential (line 4752) | def _execute_tool_calls_sequential(self, assistant_message, messages: ...
    method _get_budget_warning (line 5030) | def _get_budget_warning(self, api_call_count: int) -> Optional[str]:
    method _emit_context_pressure (line 5054) | def _emit_context_pressure(self, compaction_progress: float, compresso...
    method _handle_max_iterations (line 5093) | def _handle_max_iterations(self, messages: list, api_call_count: int) ...
    method run_conversation (line 5242) | def run_conversation(
    method chat (line 7010) | def chat(self, message: str, stream_callback: Optional[callable] = Non...
  function main (line 7025) | def main(

FILE: scripts/discord-voice-doctor.py
  function mask (line 33) | def mask(value):
  function check (line 40) | def check(label, ok, detail=""):
  function warn (line 49) | def warn(label, detail=""):
  function section (line 56) | def section(title):
  function check_packages (line 60) | def check_packages():
  function check_system_tools (line 121) | def check_system_tools():
  function check_env_vars (line 173) | def check_env_vars():
  function check_config (line 234) | def check_config(groq_key, eleven_key):
  function check_bot_permissions (line 274) | def check_bot_permissions(token):
  function main (line 363) | def main():

FILE: scripts/release.py
  function git (line 118) | def git(*args, cwd=None):
  function get_last_tag (line 131) | def get_last_tag():
  function get_current_version (line 139) | def get_current_version():
  function bump_version (line 146) | def bump_version(current: str, part: str) -> str:
  function update_version_files (line 168) | def update_version_files(semver: str, calver_date: str):
  function resolve_author (line 195) | def resolve_author(name: str, email: str) -> str:
  function categorize_commit (line 216) | def categorize_commit(subject: str) -> str:
  function clean_subject (line 250) | def clean_subject(subject: str) -> str:
  function get_commits (line 262) | def get_commits(since_tag=None):
  function get_pr_number (line 300) | def get_pr_number(subject: str) -> str:
  function generate_changelog (line 308) | def generate_changelog(commits, tag_name, semver, repo_url="https://gith...
  function main (line 406) | def main():

FILE: scripts/sample_and_compress.py
  function load_dataset_from_hf (line 40) | def load_dataset_from_hf(dataset_name: str) -> List[Dict[str, Any]]:
  function _init_tokenizer_worker (line 82) | def _init_tokenizer_worker(tokenizer_name: str):
  function _count_tokens_for_entry (line 89) | def _count_tokens_for_entry(entry: Dict) -> Tuple[Dict, int]:
  function sample_from_datasets (line 118) | def sample_from_datasets(
  function save_samples_for_compression (line 226) | def save_samples_for_compression(
  function run_compression (line 260) | def run_compression(input_dir: Path, output_dir: Path, config_path: str):
  function merge_output_to_single_jsonl (line 289) | def merge_output_to_single_jsonl(input_dir: Path, output_file: Path):
  function main (line 318) | def main(

FILE: scripts/whatsapp-bridge/bridge.js
  function getArg (line 32) | function getArg(name, defaultVal) {
  constant WHATSAPP_DEBUG (line 37) | const WHATSAPP_DEBUG =
  constant PORT (line 43) | const PORT = parseInt(getArg('port', '3000'), 10);
  constant SESSION_DIR (line 44) | const SESSION_DIR = getArg('session', path.join(process.env.HOME || '~',...
  constant IMAGE_CACHE_DIR (line 45) | const IMAGE_CACHE_DIR = path.join(process.env.HOME || '~', '.hermes', 'i...
  constant PAIR_ONLY (line 46) | const PAIR_ONLY = args.includes('--pair-only');
  constant WHATSAPP_MODE (line 47) | const WHATSAPP_MODE = getArg('mode', process.env.WHATSAPP_MODE || 'self-...
  constant ALLOWED_USERS (line 48) | const ALLOWED_USERS = (process.env.WHATSAPP_ALLOWED_USERS || '').split('...
  constant DEFAULT_REPLY_PREFIX (line 49) | const DEFAULT_REPLY_PREFIX = '⚕ *Hermes Agent*\n────────────\n';
  constant REPLY_PREFIX (line 50) | const REPLY_PREFIX = process.env.WHATSAPP_REPLY_PREFIX === undefined
  function formatOutgoingMessage (line 54) | function formatOutgoingMessage(message) {
  function buildLidMap (line 61) | function buildLidMap() {
  constant MAX_QUEUE_SIZE (line 80) | const MAX_QUEUE_SIZE = 100;
  constant MAX_RECENT_IDS (line 84) | const MAX_RECENT_IDS = 50;
  function startSocket (line 89) | async function startSocket() {
  constant MIME_MAP (line 342) | const MIME_MAP = {
  function inferMediaType (line 353) | function inferMediaType(ext) {

FILE: skills/creative/excalidraw/scripts/upload.py
  function concat_buffers (line 39) | def concat_buffers(*buffers: bytes) -> bytes:
  function upload (line 53) | def upload(excalidraw_json: str) -> str:
  function main (line 104) | def main():

FILE: skills/leisure/find-nearby/scripts/find_nearby.py
  function _http_get (line 36) | def _http_get(url: str) -> Any:
  function _http_post (line 42) | def _http_post(url: str, data: str) -> Any:
  function haversine (line 50) | def haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> flo
Copy disabled (too large) Download .json
Condensed preview — 1064 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (17,506K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 4337,
    "preview": "name: \"🐛 Bug Report\"\ndescription: Report a bug — something that's broken, crashes, or behaves incorrectly.\ntitle: \"[Bug]"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 524,
    "preview": "blank_issues_enabled: true\ncontact_links:\n  - name: 💬 Nous Research Discord\n    url: https://discord.gg/NousResearch\n   "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 2399,
    "preview": "name: \"✨ Feature Request\"\ndescription: Suggest a new feature or improvement.\ntitle: \"[Feature]: \"\nlabels: [\"enhancement\""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/setup_help.yml",
    "chars": 3041,
    "preview": "name: \"🔧 Setup / Installation Help\"\ndescription: Having trouble installing or configuring Hermes? Ask here.\ntitle: \"[Set"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 3080,
    "preview": "## What does this PR do?\n\n<!-- Describe the change clearly. What problem does it solve? Why is this approach the right o"
  },
  {
    "path": ".github/workflows/deploy-site.yml",
    "chars": 1378,
    "preview": "name: Deploy Site\n\non:\n  push:\n    branches: [main]\n    paths:\n      - 'website/**'\n      - 'landingpage/**'\n      - '.g"
  },
  {
    "path": ".github/workflows/docs-site-checks.yml",
    "chars": 868,
    "preview": "name: Docs Site Checks\n\non:\n  pull_request:\n    paths:\n      - 'website/**'\n      - '.github/workflows/docs-site-checks."
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 963,
    "preview": "name: Tests\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\n# Cancel in-progress runs for the sa"
  },
  {
    "path": ".gitignore",
    "chars": 913,
    "preview": "/venv/\n/_pycache/\n*.pyc*\n__pycache__/\n.venv/\n.vscode/\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.produc"
  },
  {
    "path": ".gitmodules",
    "chars": 209,
    "preview": "[submodule \"mini-swe-agent\"]\n\tpath = mini-swe-agent\n\turl = https://github.com/SWE-agent/mini-swe-agent\n[submodule \"tinke"
  },
  {
    "path": ".plans/openai-api-server.md",
    "chars": 10095,
    "preview": "# OpenAI-Compatible API Server for Hermes Agent\n\n## Motivation\n\nEvery major chat frontend (Open WebUI 126k★, LobeChat 73"
  },
  {
    "path": ".plans/streaming-support.md",
    "chars": 23881,
    "preview": "# Streaming LLM Response Support for Hermes Agent\n\n## Overview\n\nAdd token-by-token streaming of LLM responses across all"
  },
  {
    "path": "AGENTS.md",
    "chars": 16107,
    "preview": "# Hermes Agent - Development Guide\n\nInstructions for AI coding assistants and developers working on the hermes-agent cod"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 26182,
    "preview": "# Contributing to Hermes Agent\n\nThank you for contributing to Hermes Agent! This guide covers everything you need: setti"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2025 Nous Research\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 10627,
    "preview": "<p align=\"center\">\n  <img src=\"assets/banner.png\" alt=\"Hermes Agent\" width=\"100%\">\n</p>\n\n# Hermes Agent ☤\n\n<p align=\"cen"
  },
  {
    "path": "RELEASE_v0.2.0.md",
    "chars": 31963,
    "preview": "# Hermes Agent v0.2.0 (v2026.3.12)\n\n**Release Date:** March 12, 2026\n\n> First tagged release since v0.1.0 (the initial p"
  },
  {
    "path": "RELEASE_v0.3.0.md",
    "chars": 35503,
    "preview": "# Hermes Agent v0.3.0 (v2026.3.17)\n\n**Release Date:** March 17, 2026\n\n> The streaming, plugins, and provider release — u"
  },
  {
    "path": "acp_adapter/__init__.py",
    "chars": 67,
    "preview": "\"\"\"ACP (Agent Communication Protocol) adapter for hermes-agent.\"\"\"\n"
  },
  {
    "path": "acp_adapter/__main__.py",
    "chars": 99,
    "preview": "\"\"\"Allow running the ACP adapter as ``python -m acp_adapter``.\"\"\"\n\nfrom .entry import main\n\nmain()\n"
  },
  {
    "path": "acp_adapter/auth.py",
    "chars": 829,
    "preview": "\"\"\"ACP auth helpers — detect the currently configured Hermes provider.\"\"\"\n\nfrom __future__ import annotations\n\nfrom typi"
  },
  {
    "path": "acp_adapter/entry.py",
    "chars": 2402,
    "preview": "\"\"\"CLI entry point for the hermes-agent ACP adapter.\n\nLoads environment variables from ``~/.hermes/.env``, configures lo"
  },
  {
    "path": "acp_adapter/events.py",
    "chars": 5305,
    "preview": "\"\"\"Callback factories for bridging AIAgent events to ACP notifications.\n\nEach factory returns a callable with the signat"
  },
  {
    "path": "acp_adapter/permissions.py",
    "chars": 2618,
    "preview": "\"\"\"ACP permission bridging — maps ACP approval requests to hermes approval callbacks.\"\"\"\n\nfrom __future__ import annotat"
  },
  {
    "path": "acp_adapter/server.py",
    "chars": 18563,
    "preview": "\"\"\"ACP agent server — exposes Hermes Agent via the Agent Client Protocol.\"\"\"\n\nfrom __future__ import annotations\n\nimport"
  },
  {
    "path": "acp_adapter/session.py",
    "chars": 16041,
    "preview": "\"\"\"ACP session manager — maps ACP sessions to Hermes AIAgent instances.\n\nSessions are persisted to the shared SessionDB "
  },
  {
    "path": "acp_adapter/tools.py",
    "chars": 7296,
    "preview": "\"\"\"ACP tool-call helpers for mapping hermes tools to ACP ToolKind and building content.\"\"\"\n\nfrom __future__ import annot"
  },
  {
    "path": "acp_registry/agent.json",
    "chars": 309,
    "preview": "{\n  \"schema_version\": 1,\n  \"name\": \"hermes-agent\",\n  \"display_name\": \"Hermes Agent\",\n  \"description\": \"AI agent by Nous "
  },
  {
    "path": "agent/__init__.py",
    "chars": 276,
    "preview": "\"\"\"Agent internals -- extracted modules from run_agent.py.\n\nThese modules contain pure utility functions and self-contai"
  },
  {
    "path": "agent/anthropic_adapter.py",
    "chars": 43993,
    "preview": "\"\"\"Anthropic Messages API adapter for Hermes Agent.\n\nTranslates between Hermes's internal OpenAI-style message format an"
  },
  {
    "path": "agent/auxiliary_client.py",
    "chars": 60078,
    "preview": "\"\"\"Shared auxiliary client router for side tasks.\n\nProvides a single resolution chain so every consumer (context compres"
  },
  {
    "path": "agent/context_compressor.py",
    "chars": 17541,
    "preview": "\"\"\"Automatic context window compression for long conversations.\n\nSelf-contained class with its own OpenAI client for sum"
  },
  {
    "path": "agent/copilot_acp_client.py",
    "chars": 15394,
    "preview": "\"\"\"OpenAI-compatible shim that forwards Hermes requests to `copilot --acp`.\n\nThis adapter lets Hermes treat the GitHub C"
  },
  {
    "path": "agent/display.py",
    "chars": 28815,
    "preview": "\"\"\"CLI presentation -- spinner, kawaii faces, tool preview formatting.\n\nPure display functions and classes with no AIAge"
  },
  {
    "path": "agent/insights.py",
    "chars": 33273,
    "preview": "\"\"\"\nSession Insights Engine for Hermes Agent.\n\nAnalyzes historical session data from the SQLite state database to produc"
  },
  {
    "path": "agent/model_metadata.py",
    "chars": 34470,
    "preview": "\"\"\"Model metadata, context lengths, and token estimation utilities.\n\nPure utility functions with no AIAgent dependency. "
  },
  {
    "path": "agent/models_dev.py",
    "chars": 5449,
    "preview": "\"\"\"Models.dev registry integration for provider-aware context length detection.\n\nFetches model metadata from https://mod"
  },
  {
    "path": "agent/prompt_builder.py",
    "chars": 24003,
    "preview": "\"\"\"System prompt assembly -- identity, platform hints, skills index, context files.\n\nAll functions are stateless. AIAgen"
  },
  {
    "path": "agent/prompt_caching.py",
    "chars": 2079,
    "preview": "\"\"\"Anthropic prompt caching (system_and_3 strategy).\n\nReduces input token costs by ~75% on multi-turn conversations by c"
  },
  {
    "path": "agent/redact.py",
    "chars": 5937,
    "preview": "\"\"\"Regex-based secret redaction for logs and tool output.\n\nApplies pattern matching to mask API keys, tokens, and creden"
  },
  {
    "path": "agent/skill_commands.py",
    "chars": 9952,
    "preview": "\"\"\"Shared slash command helpers for skills and built-in prompt-style modes.\n\nShared between CLI (cli.py) and gateway (ga"
  },
  {
    "path": "agent/smart_model_routing.py",
    "chars": 5716,
    "preview": "\"\"\"Helpers for optional cheap-vs-strong model routing.\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport re\nfrom "
  },
  {
    "path": "agent/title_generator.py",
    "chars": 4166,
    "preview": "\"\"\"Auto-generate short session titles from the first user/assistant exchange.\n\nRuns asynchronously after the first respo"
  },
  {
    "path": "agent/trajectory.py",
    "chars": 2028,
    "preview": "\"\"\"Trajectory saving utilities and static helpers.\n\n_convert_to_trajectory_format stays as an AIAgent method (batch_runn"
  },
  {
    "path": "agent/usage_pricing.py",
    "chars": 23514,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nfrom decim"
  },
  {
    "path": "batch_runner.py",
    "chars": 54973,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nBatch Agent Runner\n\nThis module provides parallel batch processing capabilities for running t"
  },
  {
    "path": "cli-config.yaml.example",
    "chars": 37471,
    "preview": "# Hermes Agent CLI Configuration\n# Copy this file to cli-config.yaml and customize as needed.\n# This file configures the"
  },
  {
    "path": "cli.py",
    "chars": 318736,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nHermes Agent CLI - Interactive Terminal Interface\n\nA beautiful command-line interface for the"
  },
  {
    "path": "cron/__init__.py",
    "chars": 1063,
    "preview": "\"\"\"\nCron job scheduling system for Hermes Agent.\n\nThis module provides scheduled task execution, allowing the agent to:\n"
  },
  {
    "path": "cron/jobs.py",
    "chars": 22183,
    "preview": "\"\"\"\nCron job storage and management.\n\nJobs are stored in ~/.hermes/cron/jobs.json\nOutput is saved to ~/.hermes/cron/outp"
  },
  {
    "path": "cron/scheduler.py",
    "chars": 19970,
    "preview": "\"\"\"\nCron job scheduler - executes due jobs.\n\nProvides tick() which checks for due jobs and runs them. The gateway\ncalls "
  },
  {
    "path": "datagen-config-examples/example_browser_tasks.jsonl",
    "chars": 1015,
    "preview": "{\"prompt\": \"Go to https://news.ycombinator.com and find the top 5 posts on the front page. For each post, get the title,"
  },
  {
    "path": "datagen-config-examples/run_browser_tasks.sh",
    "chars": 2964,
    "preview": "#!/bin/bash\n\n# =============================================================================\n# Example: Browser-Focused "
  },
  {
    "path": "datagen-config-examples/trajectory_compression.yaml",
    "chars": 3046,
    "preview": "# Trajectory Compression Configuration\n# \n# Post-processes completed agent trajectories to fit within a target token bud"
  },
  {
    "path": "datagen-config-examples/web_research.yaml",
    "chars": 1324,
    "preview": "# datagen-config-examples/web_research.yaml\n#\n# Batch data generation config for WebResearchEnv.\n# Generates tool-callin"
  },
  {
    "path": "docs/acp-setup.md",
    "chars": 5866,
    "preview": "# Hermes Agent — ACP (Agent Client Protocol) Setup Guide\n\nHermes Agent supports the **Agent Client Protocol (ACP)**, all"
  },
  {
    "path": "docs/honcho-integration-spec.html",
    "chars": 38456,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initia"
  },
  {
    "path": "docs/honcho-integration-spec.md",
    "chars": 15240,
    "preview": "# honcho-integration-spec\n\nComparison of Hermes Agent vs. openclaw-honcho — and a porting spec for bringing Hermes patte"
  },
  {
    "path": "docs/migration/openclaw.md",
    "chars": 4921,
    "preview": "# Migrating from OpenClaw to Hermes Agent\n\nThis guide covers how to import your OpenClaw settings, memories, skills, and"
  },
  {
    "path": "docs/plans/2026-03-16-pricing-accuracy-architecture-design.md",
    "chars": 14688,
    "preview": "# Pricing Accuracy Architecture\n\nDate: 2026-03-16\n\n## Goal\n\nHermes should only show dollar costs when they are backed by"
  },
  {
    "path": "docs/skins/example-skin.yaml",
    "chars": 3489,
    "preview": "# ============================================================================\n# Hermes Agent — Example Skin Template\n# "
  },
  {
    "path": "environments/README.md",
    "chars": 14526,
    "preview": "# Hermes-Agent Atropos Environments\n\nThis directory contains the integration layer between **hermes-agent's** tool-calli"
  },
  {
    "path": "environments/__init__.py",
    "chars": 1288,
    "preview": "\"\"\"\nHermes-Agent Atropos Environments\n\nProvides a layered integration between hermes-agent's tool-calling capabilities\na"
  },
  {
    "path": "environments/agent_loop.py",
    "chars": 22218,
    "preview": "\"\"\"\nHermesAgentLoop -- Reusable Multi-Turn Agent Engine\n\nRuns the hermes-agent tool-calling loop using standard OpenAI-s"
  },
  {
    "path": "environments/agentic_opd_env.py",
    "chars": 47705,
    "preview": "\"\"\"\nAgenticOPDEnv — On-Policy Distillation for Agentic Tool-Calling Tasks\n=============================================="
  },
  {
    "path": "environments/benchmarks/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/benchmarks/tblite/README.md",
    "chars": 3699,
    "preview": "# OpenThoughts-TBLite Evaluation Environment\n\nThis environment evaluates terminal agents on the [OpenThoughts-TBLite](ht"
  },
  {
    "path": "environments/benchmarks/tblite/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/benchmarks/tblite/default.yaml",
    "chars": 1501,
    "preview": "# OpenThoughts-TBLite Evaluation -- Default Configuration\n#\n# Eval-only environment for the TBLite benchmark (100 diffic"
  },
  {
    "path": "environments/benchmarks/tblite/local.yaml",
    "chars": 1284,
    "preview": "# OpenThoughts-TBLite Evaluation -- Docker Backend (Local Compute)\n#\n# Runs tasks in Docker containers on the local mach"
  },
  {
    "path": "environments/benchmarks/tblite/local_vllm.yaml",
    "chars": 1576,
    "preview": "# OpenThoughts-TBLite Evaluation -- Local vLLM Backend\n#\n# Runs against a local vLLM server with Docker sandboxes.\n#\n# S"
  },
  {
    "path": "environments/benchmarks/tblite/run_eval.sh",
    "chars": 1113,
    "preview": "#!/bin/bash\n\n# OpenThoughts-TBLite Evaluation\n#\n# Run from repo root:\n#   bash environments/benchmarks/tblite/run_eval.s"
  },
  {
    "path": "environments/benchmarks/tblite/tblite_env.py",
    "chars": 3691,
    "preview": "\"\"\"\nOpenThoughts-TBLite Evaluation Environment\n\nA lighter, faster alternative to Terminal-Bench 2.0 for iterating on ter"
  },
  {
    "path": "environments/benchmarks/terminalbench_2/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/benchmarks/terminalbench_2/default.yaml",
    "chars": 1729,
    "preview": "# Terminal-Bench 2.0 Evaluation -- Default Configuration\n#\n# Eval-only environment for the TB2 benchmark (89 terminal ta"
  },
  {
    "path": "environments/benchmarks/terminalbench_2/run_eval.sh",
    "chars": 1147,
    "preview": "#!/bin/bash\n\n# Terminal-Bench 2.0 Evaluation\n#\n# Run from repo root:\n#   bash environments/benchmarks/terminalbench_2/ru"
  },
  {
    "path": "environments/benchmarks/terminalbench_2/terminalbench2_env.py",
    "chars": 21636,
    "preview": "\"\"\"\nTerminalBench2Env -- Terminal-Bench 2.0 Evaluation Environment\n\nEvaluates agentic LLMs on challenging terminal tasks"
  },
  {
    "path": "environments/benchmarks/yc_bench/README.md",
    "chars": 4429,
    "preview": "# YC-Bench: Long-Horizon Agent Benchmark\n\n[YC-Bench](https://github.com/collinear-ai/yc-bench) by [Collinear AI](https:/"
  },
  {
    "path": "environments/benchmarks/yc_bench/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/benchmarks/yc_bench/default.yaml",
    "chars": 1585,
    "preview": "# YC-Bench Evaluation -- Default Configuration\n#\n# Long-horizon agent benchmark: agent plays CEO of an AI startup over\n#"
  },
  {
    "path": "environments/benchmarks/yc_bench/run_eval.sh",
    "chars": 850,
    "preview": "#!/bin/bash\n\n# YC-Bench Evaluation\n#\n# Requires: pip install \"hermes-agent[yc-bench]\"\n#\n# Run from repo root:\n#   bash e"
  },
  {
    "path": "environments/benchmarks/yc_bench/yc_bench_env.py",
    "chars": 32569,
    "preview": "\"\"\"\nYCBenchEvalEnv -- YC-Bench Long-Horizon Agent Benchmark Environment\n\nEvaluates agentic LLMs on YC-Bench: a determini"
  },
  {
    "path": "environments/hermes_base_env.py",
    "chars": 27086,
    "preview": "\"\"\"\nHermesAgentBaseEnv -- Abstract Base Environment for Hermes-Agent + Atropos\n\nProvides the Atropos integration plumbin"
  },
  {
    "path": "environments/hermes_swe_env/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/hermes_swe_env/default.yaml",
    "chars": 1089,
    "preview": "# SWE Environment -- Default Configuration\n#\n# SWE-bench style tasks with Modal sandboxes for cloud isolation.\n# Uses te"
  },
  {
    "path": "environments/hermes_swe_env/hermes_swe_env.py",
    "chars": 8191,
    "preview": "\"\"\"\nHermesSweEnv -- SWE-Bench Style Environment with Modal Sandboxes\n\nA concrete environment for software engineering ta"
  },
  {
    "path": "environments/patches.py",
    "chars": 7812,
    "preview": "\"\"\"\nMonkey patches for making hermes-agent tools work inside async frameworks (Atropos).\n\nProblem:\n    Some tools use as"
  },
  {
    "path": "environments/terminal_test_env/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "environments/terminal_test_env/default.yaml",
    "chars": 1123,
    "preview": "# Terminal Test Environment -- Default Configuration\n#\n# Simple file-creation tasks for validating the full Atropos + he"
  },
  {
    "path": "environments/terminal_test_env/terminal_test_env.py",
    "chars": 10424,
    "preview": "\"\"\"\nTerminalTestEnv -- Simple Test Environment for Validating the Stack\n\nA self-contained environment with inline tasks "
  },
  {
    "path": "environments/tool_call_parsers/__init__.py",
    "chars": 4284,
    "preview": "\"\"\"\nTool Call Parser Registry\n\nClient-side parsers that extract structured tool_calls from raw model output text.\nUsed i"
  },
  {
    "path": "environments/tool_call_parsers/deepseek_v3_1_parser.py",
    "chars": 2205,
    "preview": "\"\"\"\nDeepSeek V3.1 tool call parser.\n\nSimilar to V3 but with a slightly different format:\n    <|tool▁call▁begin|>function"
  },
  {
    "path": "environments/tool_call_parsers/deepseek_v3_parser.py",
    "chars": 2895,
    "preview": "\"\"\"\nDeepSeek V3 tool call parser.\n\nFormat uses special unicode tokens:\n    <|tool▁calls▁begin|>\n    <|tool▁call▁begin|>t"
  },
  {
    "path": "environments/tool_call_parsers/glm45_parser.py",
    "chars": 3364,
    "preview": "\"\"\"\nGLM 4.5 (GLM-4-MoE) tool call parser.\n\nFormat uses custom arg_key/arg_value tags rather than standard JSON:\n    <too"
  },
  {
    "path": "environments/tool_call_parsers/glm47_parser.py",
    "chars": 1140,
    "preview": "\"\"\"\nGLM 4.7 tool call parser.\n\nSame as GLM 4.5 but with slightly different regex patterns.\nThe tool_call tags may wrap d"
  },
  {
    "path": "environments/tool_call_parsers/hermes_parser.py",
    "chars": 2321,
    "preview": "\"\"\"\nHermes tool call parser.\n\nFormat: <tool_call>{\"name\": \"func\", \"arguments\": {...}}</tool_call>\nBased on VLLM's Hermes"
  },
  {
    "path": "environments/tool_call_parsers/kimi_k2_parser.py",
    "chars": 3023,
    "preview": "\"\"\"\nKimi K2 tool call parser.\n\nFormat:\n    <|tool_calls_section_begin|>\n    <|tool_call_begin|>function_id:0<|tool_call_"
  },
  {
    "path": "environments/tool_call_parsers/llama_parser.py",
    "chars": 3354,
    "preview": "\"\"\"\nLlama 3.x / 4 tool call parser.\n\nFormat: The model outputs JSON objects with \"name\" and \"arguments\" (or \"parameters\""
  },
  {
    "path": "environments/tool_call_parsers/longcat_parser.py",
    "chars": 2094,
    "preview": "\"\"\"\nLongcat Flash Chat tool call parser.\n\nSame as Hermes but uses <longcat_tool_call> tags instead of <tool_call>.\nBased"
  },
  {
    "path": "environments/tool_call_parsers/mistral_parser.py",
    "chars": 4932,
    "preview": "\"\"\"\nMistral tool call parser.\n\nSupports two formats depending on tokenizer version:\n- Pre-v11: content[TOOL_CALLS] [{\"na"
  },
  {
    "path": "environments/tool_call_parsers/qwen3_coder_parser.py",
    "chars": 5257,
    "preview": "\"\"\"\nQwen3-Coder tool call parser.\n\nFormat uses XML-style nested tags:\n    <tool_call>\n    <function=function_name>\n    <"
  },
  {
    "path": "environments/tool_call_parsers/qwen_parser.py",
    "chars": 572,
    "preview": "\"\"\"\nQwen 2.5 tool call parser.\n\nUses the same <tool_call> format as Hermes.\nRegistered as a separate parser name for cla"
  },
  {
    "path": "environments/tool_context.py",
    "chars": 16650,
    "preview": "\"\"\"\nToolContext -- Unrestricted Tool Access for Reward Functions\n\nA per-rollout handle that gives reward/verification fu"
  },
  {
    "path": "environments/web_research_env.py",
    "chars": 28879,
    "preview": "\"\"\"\nWebResearchEnv — RL Environment for Multi-Step Web Research\n========================================================"
  },
  {
    "path": "gateway/__init__.py",
    "chars": 1020,
    "preview": "\"\"\"\nHermes Gateway - Multi-platform messaging integration.\n\nThis module provides a unified gateway for connecting the He"
  },
  {
    "path": "gateway/channel_directory.py",
    "chars": 8781,
    "preview": "\"\"\"\nChannel directory -- cached map of reachable channels/contacts per platform.\n\nBuilt on gateway startup, refreshed pe"
  },
  {
    "path": "gateway/config.py",
    "chars": 30702,
    "preview": "\"\"\"\nGateway configuration management.\n\nHandles loading and validating configuration for:\n- Connected platforms (Telegram"
  },
  {
    "path": "gateway/delivery.py",
    "chars": 11351,
    "preview": "\"\"\"\nDelivery routing for cron job outputs and agent responses.\n\nRoutes messages to the appropriate destination based on:"
  },
  {
    "path": "gateway/hooks.py",
    "chars": 5708,
    "preview": "\"\"\"\nEvent Hook System\n\nA lightweight event-driven system that fires handlers at key lifecycle points.\nHooks are discover"
  },
  {
    "path": "gateway/mirror.py",
    "chars": 4349,
    "preview": "\"\"\"\nSession mirroring for cross-platform message delivery.\n\nWhen a message is sent to a platform (via send_message or cr"
  },
  {
    "path": "gateway/pairing.py",
    "chars": 10406,
    "preview": "\"\"\"\nDM Pairing System\n\nCode-based approval flow for authorizing new users on messaging platforms.\nInstead of static allo"
  },
  {
    "path": "gateway/platforms/ADDING_A_PLATFORM.md",
    "chars": 8813,
    "preview": "# Adding a New Messaging Platform\n\nChecklist for integrating a new messaging platform into the Hermes gateway.\nUse this "
  },
  {
    "path": "gateway/platforms/__init__.py",
    "chars": 368,
    "preview": "\"\"\"\nPlatform adapters for messaging integrations.\n\nEach adapter handles:\n- Receiving messages from a platform\n- Sending "
  },
  {
    "path": "gateway/platforms/api_server.py",
    "chars": 30467,
    "preview": "\"\"\"\nOpenAI-compatible API server platform adapter.\n\nExposes an HTTP server with endpoints:\n- POST /v1/chat/completions  "
  },
  {
    "path": "gateway/platforms/base.py",
    "chars": 51668,
    "preview": "\"\"\"\nBase platform adapter interface.\n\nAll platform adapters (Telegram, Discord, WhatsApp) inherit from this\nand implemen"
  },
  {
    "path": "gateway/platforms/dingtalk.py",
    "chars": 12841,
    "preview": "\"\"\"\nDingTalk platform adapter using Stream Mode.\n\nUses dingtalk-stream SDK for real-time message reception without webho"
  },
  {
    "path": "gateway/platforms/discord.py",
    "chars": 88234,
    "preview": "from __future__ import annotations\n\n\"\"\"\nDiscord platform adapter.\n\nUses discord.py library for:\n- Receiving messages fro"
  },
  {
    "path": "gateway/platforms/email.py",
    "chars": 19668,
    "preview": "\"\"\"\nEmail platform adapter for the Hermes gateway.\n\nAllows users to interact with Hermes by sending emails.\nUses IMAP to"
  },
  {
    "path": "gateway/platforms/homeassistant.py",
    "chars": 16119,
    "preview": "\"\"\"\nHome Assistant platform adapter.\n\nConnects to the HA WebSocket API for real-time event monitoring.\nState-change even"
  },
  {
    "path": "gateway/platforms/matrix.py",
    "chars": 31870,
    "preview": "\"\"\"Matrix gateway adapter.\n\nConnects to any Matrix homeserver (self-hosted or matrix.org) via the\nmatrix-nio Python SDK."
  },
  {
    "path": "gateway/platforms/mattermost.py",
    "chars": 23925,
    "preview": "\"\"\"Mattermost gateway adapter.\n\nConnects to a self-hosted (or cloud) Mattermost instance via its REST API\n(v4) and WebSo"
  },
  {
    "path": "gateway/platforms/signal.py",
    "chars": 28689,
    "preview": "\"\"\"Signal messenger platform adapter.\n\nConnects to a signal-cli daemon running in HTTP mode.\nInbound messages arrive via"
  },
  {
    "path": "gateway/platforms/slack.py",
    "chars": 31790,
    "preview": "\"\"\"\nSlack platform adapter.\n\nUses slack-bolt (Python) with Socket Mode for:\n- Receiving messages from channels and DMs\n-"
  },
  {
    "path": "gateway/platforms/sms.py",
    "chars": 9913,
    "preview": "\"\"\"SMS (Twilio) platform adapter.\n\nConnects to the Twilio REST API for outbound SMS and runs an aiohttp\nwebhook server t"
  },
  {
    "path": "gateway/platforms/telegram.py",
    "chars": 57688,
    "preview": "\"\"\"\nTelegram platform adapter.\n\nUses python-telegram-bot library for:\n- Receiving messages from users/groups\n- Sending r"
  },
  {
    "path": "gateway/platforms/webhook.py",
    "chars": 20324,
    "preview": "\"\"\"Generic webhook platform adapter.\n\nRuns an aiohttp HTTP server that receives webhook POSTs from external\nservices (Gi"
  },
  {
    "path": "gateway/platforms/whatsapp.py",
    "chars": 27681,
    "preview": "\"\"\"\nWhatsApp platform adapter.\n\nWhatsApp integration is more complex than Telegram/Discord because:\n- No official bot AP"
  },
  {
    "path": "gateway/run.py",
    "chars": 238490,
    "preview": "\"\"\"\nGateway runner - entry point for messaging platform integrations.\n\nThis module provides:\n- start_gateway(): Start al"
  },
  {
    "path": "gateway/session.py",
    "chars": 36728,
    "preview": "\"\"\"\nSession management for the gateway.\n\nHandles:\n- Session context tracking (where messages come from)\n- Session storag"
  },
  {
    "path": "gateway/status.py",
    "chars": 11019,
    "preview": "\"\"\"\nGateway runtime status helpers.\n\nProvides PID-file based detection of whether the gateway daemon is running,\nused by"
  },
  {
    "path": "gateway/sticker_cache.py",
    "chars": 3129,
    "preview": "\"\"\"\nSticker description cache for Telegram.\n\nWhen users send stickers, we describe them via the vision tool and cache\nth"
  },
  {
    "path": "gateway/stream_consumer.py",
    "chars": 8100,
    "preview": "\"\"\"Gateway streaming consumer — bridges sync agent callbacks to async platform delivery.\n\nThe agent fires stream_delta_c"
  },
  {
    "path": "hermes",
    "chars": 229,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nHermes Agent CLI Launcher\n\nThis is a convenience wrapper to launch the Hermes CLI.\nUsage: ./h"
  },
  {
    "path": "hermes_cli/__init__.py",
    "chars": 505,
    "preview": "\"\"\"\nHermes CLI - Unified command-line interface for Hermes Agent.\n\nProvides subcommands for:\n- hermes chat          - In"
  },
  {
    "path": "hermes_cli/auth.py",
    "chars": 87121,
    "preview": "\"\"\"\nMulti-provider authentication system for Hermes Agent.\n\nSupports OAuth device code flows (Nous Portal, future: OpenA"
  },
  {
    "path": "hermes_cli/banner.py",
    "chars": 16559,
    "preview": "\"\"\"Welcome banner, ASCII art, skills summary, and update check for the CLI.\n\nPure display functions with no HermesCLI st"
  },
  {
    "path": "hermes_cli/callbacks.py",
    "chars": 9361,
    "preview": "\"\"\"Interactive prompt callbacks for terminal_tool integration.\n\nThese bridge terminal_tool's interactive prompts (clarif"
  },
  {
    "path": "hermes_cli/checklist.py",
    "chars": 4820,
    "preview": "\"\"\"Shared curses-based multi-select checklist for Hermes CLI.\n\nUsed by both ``hermes tools`` and ``hermes skills`` to pr"
  },
  {
    "path": "hermes_cli/claw.py",
    "chars": 10540,
    "preview": "\"\"\"hermes claw — OpenClaw migration commands.\n\nUsage:\n    hermes claw migrate              # Interactive migration from "
  },
  {
    "path": "hermes_cli/clipboard.py",
    "chars": 11609,
    "preview": "\"\"\"Clipboard image extraction for macOS, Linux, and WSL2.\n\nProvides a single function `save_clipboard_image(dest)` that "
  },
  {
    "path": "hermes_cli/codex_models.py",
    "chars": 5748,
    "preview": "\"\"\"Codex model discovery from API, local cache, and config.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport l"
  },
  {
    "path": "hermes_cli/colors.py",
    "chars": 490,
    "preview": "\"\"\"Shared ANSI color utilities for Hermes CLI modules.\"\"\"\n\nimport sys\n\n\nclass Colors:\n    RESET = \"\\033[0m\"\n    BOLD = \""
  },
  {
    "path": "hermes_cli/commands.py",
    "chars": 25127,
    "preview": "\"\"\"Slash command definitions and autocomplete for the Hermes CLI.\n\nCentral registry for all slash commands. Every consum"
  },
  {
    "path": "hermes_cli/config.py",
    "chars": 72317,
    "preview": "\"\"\"\nConfiguration management for Hermes Agent.\n\nConfig files are stored in ~/.hermes/ for easy access:\n- ~/.hermes/confi"
  },
  {
    "path": "hermes_cli/copilot_auth.py",
    "chars": 9568,
    "preview": "\"\"\"GitHub Copilot authentication utilities.\n\nImplements the OAuth device code flow used by the Copilot CLI and handles\nt"
  },
  {
    "path": "hermes_cli/cron.py",
    "chars": 9178,
    "preview": "\"\"\"\nCron subcommand for hermes CLI.\n\nHandles standalone cron management commands like list, create, edit,\npause/resume/r"
  },
  {
    "path": "hermes_cli/curses_ui.py",
    "chars": 4960,
    "preview": "\"\"\"Shared curses-based UI components for Hermes CLI.\n\nUsed by `hermes tools` and `hermes skills` for interactive checkli"
  },
  {
    "path": "hermes_cli/default_soul.py",
    "chars": 6385,
    "preview": "\"\"\"Default SOUL.md template seeded into HERMES_HOME on first run.\"\"\"\n\nDEFAULT_SOUL_MD = \"\"\"# Hermes ☤\n\nYou are Hermes, a"
  },
  {
    "path": "hermes_cli/doctor.py",
    "chars": 33807,
    "preview": "\"\"\"\nDoctor command for hermes CLI.\n\nDiagnoses issues with Hermes Agent setup.\n\"\"\"\n\nimport os\nimport sys\nimport subproces"
  },
  {
    "path": "hermes_cli/env_loader.py",
    "chars": 1506,
    "preview": "\"\"\"Helpers for loading Hermes .env files consistently across entrypoints.\"\"\"\n\nfrom __future__ import annotations\n\nimport"
  },
  {
    "path": "hermes_cli/gateway.py",
    "chars": 73217,
    "preview": "\"\"\"\nGateway subcommand for hermes CLI.\n\nHandles: hermes gateway [run|start|stop|restart|status|install|uninstall|setup]\n"
  },
  {
    "path": "hermes_cli/main.py",
    "chars": 159009,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nHermes CLI - Main entry point.\n\nUsage:\n    hermes                     # Interactive chat (def"
  },
  {
    "path": "hermes_cli/models.py",
    "chars": 40523,
    "preview": "\"\"\"\nCanonical model catalogs and lightweight validation helpers.\n\nAdd, remove, or reorder entries here — both `hermes se"
  },
  {
    "path": "hermes_cli/pairing.py",
    "chars": 3415,
    "preview": "\"\"\"\nCLI commands for the DM pairing system.\n\nUsage:\n    hermes pairing list              # Show all pending + approved u"
  },
  {
    "path": "hermes_cli/plugins.py",
    "chars": 16267,
    "preview": "\"\"\"\nHermes Plugin System\n====================\n\nDiscovers, loads, and manages plugins from three sources:\n\n1. **User plug"
  },
  {
    "path": "hermes_cli/runtime_provider.py",
    "chars": 15901,
    "preview": "\"\"\"Shared runtime provider resolution for CLI, gateway, cron, and helpers.\"\"\"\n\nfrom __future__ import annotations\n\nimpor"
  },
  {
    "path": "hermes_cli/setup.py",
    "chars": 137348,
    "preview": "\"\"\"\nInteractive setup wizard for Hermes Agent.\n\nModular wizard with independently-runnable sections:\n  1. Model & Provid"
  },
  {
    "path": "hermes_cli/skills_config.py",
    "chars": 6412,
    "preview": "\"\"\"\nSkills configuration for Hermes Agent.\n`hermes skills` enters this module.\n\nToggle individual skills or categories o"
  },
  {
    "path": "hermes_cli/skills_hub.py",
    "chars": 43161,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSkills Hub CLI — Unified interface for the Hermes Skills Hub.\n\nPowers both:\n  - `hermes skill"
  },
  {
    "path": "hermes_cli/skin_engine.py",
    "chars": 28370,
    "preview": "\"\"\"Hermes CLI skin/theme engine.\n\nA data-driven skin system that lets users customize the CLI's visual appearance.\nSkins"
  },
  {
    "path": "hermes_cli/status.py",
    "chars": 14932,
    "preview": "\"\"\"\nStatus command for hermes CLI.\n\nShows the status of all Hermes Agent components.\n\"\"\"\n\nimport os\nimport sys\nimport su"
  },
  {
    "path": "hermes_cli/tools_config.py",
    "chars": 55277,
    "preview": "\"\"\"\nUnified tool configuration for Hermes Agent.\n\n`hermes tools` and `hermes setup tools` both enter this module.\nSelect"
  },
  {
    "path": "hermes_cli/uninstall.py",
    "chars": 10994,
    "preview": "\"\"\"\nHermes Agent Uninstaller.\n\nProvides options for:\n- Full uninstall: Remove everything including configs and data\n- Ke"
  },
  {
    "path": "hermes_constants.py",
    "chars": 625,
    "preview": "\"\"\"Shared constants for Hermes Agent.\n\nImport-safe module with no dependencies — can be imported from anywhere\nwithout r"
  },
  {
    "path": "hermes_state.py",
    "chars": 37033,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSQLite State Store for Hermes Agent.\n\nProvides persistent session storage with FTS5 full-text"
  },
  {
    "path": "hermes_time.py",
    "chars": 3778,
    "preview": "\"\"\"\nTimezone-aware clock for Hermes.\n\nProvides a single ``now()`` helper that returns a timezone-aware datetime\nbased on"
  },
  {
    "path": "honcho_integration/__init__.py",
    "chars": 355,
    "preview": "\"\"\"Honcho integration for AI-native memory.\n\nThis package is only active when honcho.enabled=true in config and\nHONCHO_A"
  },
  {
    "path": "honcho_integration/cli.py",
    "chars": 31651,
    "preview": "\"\"\"CLI commands for Honcho integration management.\n\nHandles: hermes honcho setup | status | sessions | map | peer\n\"\"\"\n\nf"
  },
  {
    "path": "honcho_integration/client.py",
    "chars": 14593,
    "preview": "\"\"\"Honcho client initialization and configuration.\n\nReads the global ~/.honcho/config.json when available, falling back\n"
  },
  {
    "path": "honcho_integration/session.py",
    "chars": 37438,
    "preview": "\"\"\"Honcho-based session management for conversation history.\"\"\"\n\nfrom __future__ import annotations\n\nimport queue\nimport"
  },
  {
    "path": "landingpage/index.html",
    "chars": 23515,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w"
  },
  {
    "path": "landingpage/script.js",
    "chars": 14846,
    "preview": "// =========================================================================\n// Hermes Agent Landing Page — Interactions"
  },
  {
    "path": "landingpage/style.css",
    "chars": 23174,
    "preview": "/* =========================================================================\n   Hermes Agent Landing Page\n   Colors: Nou"
  },
  {
    "path": "mini_swe_runner.py",
    "chars": 27567,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nMini-SWE-Agent Runner with Hermes Trajectory Format\n\nThis module provides a runner that uses "
  },
  {
    "path": "minisweagent_path.py",
    "chars": 2976,
    "preview": "\"\"\"Helpers for locating the mini-swe-agent source tree.\n\nHermes often runs from git worktrees. In that layout the worktr"
  },
  {
    "path": "model_tools.py",
    "chars": 19582,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nModel Tools Module\n\nThin orchestration layer over the tool registry. Each tool file in tools/"
  },
  {
    "path": "optional-skills/DESCRIPTION.md",
    "chars": 1009,
    "preview": "# Optional Skills\n\nOfficial skills maintained by Nous Research that are **not activated by default**.\n\nThese skills ship"
  },
  {
    "path": "optional-skills/autonomous-ai-agents/DESCRIPTION.md",
    "chars": 126,
    "preview": "Optional autonomous AI agent integrations — external coding agent CLIs\nthat can be delegated to for independent coding t"
  },
  {
    "path": "optional-skills/autonomous-ai-agents/blackbox/SKILL.md",
    "chars": 5376,
    "preview": "---\nname: blackbox\ndescription: Delegate coding tasks to Blackbox AI CLI agent. Multi-model agent with built-in judge th"
  },
  {
    "path": "optional-skills/blockchain/base/SKILL.md",
    "chars": 7784,
    "preview": "---\nname: base\ndescription: Query Base (Ethereum L2) blockchain data with USD pricing — wallet balances, token info, tra"
  },
  {
    "path": "optional-skills/blockchain/base/scripts/base_client.py",
    "chars": 37079,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nBase Blockchain CLI Tool for Hermes Agent\n------------------------------------------\nQueries "
  },
  {
    "path": "optional-skills/blockchain/solana/SKILL.md",
    "chars": 6533,
    "preview": "---\nname: solana\ndescription: Query Solana blockchain data with USD pricing — wallet balances, token portfolios with val"
  },
  {
    "path": "optional-skills/blockchain/solana/scripts/solana_client.py",
    "chars": 26019,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nSolana Blockchain CLI Tool for Hermes Agent\n--------------------------------------------\nQuer"
  },
  {
    "path": "optional-skills/creative/blender-mcp/SKILL.md",
    "chars": 4112,
    "preview": "---\nname: blender-mcp\ndescription: Control Blender directly from Hermes via socket connection to the blender-mcp addon. "
  },
  {
    "path": "optional-skills/email/agentmail/SKILL.md",
    "chars": 4119,
    "preview": "---\nname: agentmail\ndescription: Give the agent its own dedicated email inbox via AgentMail. Send, receive, and manage e"
  },
  {
    "path": "optional-skills/health/DESCRIPTION.md",
    "chars": 131,
    "preview": "Health, wellness, and biometric integration skills — BCI wearables, neurofeedback, sleep tracking, and cognitive state m"
  },
  {
    "path": "optional-skills/health/neuroskill-bci/SKILL.md",
    "chars": 16534,
    "preview": "---\nname: neuroskill-bci\ndescription: >\n  Connect to a running NeuroSkill instance and incorporate the user's real-time\n"
  },
  {
    "path": "optional-skills/health/neuroskill-bci/references/api.md",
    "chars": 8380,
    "preview": "# NeuroSkill WebSocket & HTTP API Reference\n\nNeuroSkill runs a local server (default port **8375**) discoverable via mDN"
  },
  {
    "path": "optional-skills/health/neuroskill-bci/references/metrics.md",
    "chars": 9344,
    "preview": "# NeuroSkill Metric Definitions & Interpretation Guide\n\n> **⚠️ Research Use Only:** All metrics are experimental and der"
  },
  {
    "path": "optional-skills/health/neuroskill-bci/references/protocols.md",
    "chars": 16388,
    "preview": "# NeuroSkill Guided Protocols\n\nOver 70 mind-body practices triggered by specific biometric (EXG) signals. These\nare sour"
  },
  {
    "path": "optional-skills/mcp/DESCRIPTION.md",
    "chars": 89,
    "preview": "# MCP\n\nSkills for building, testing, and deploying MCP (Model Context Protocol) servers.\n"
  },
  {
    "path": "optional-skills/mcp/fastmcp/SKILL.md",
    "chars": 8334,
    "preview": "---\nname: fastmcp\ndescription: Build, test, inspect, install, and deploy MCP servers with FastMCP in Python. Use when cr"
  },
  {
    "path": "optional-skills/mcp/fastmcp/references/fastmcp-cli.md",
    "chars": 2357,
    "preview": "# FastMCP CLI Reference\n\nUse this file when the task needs exact FastMCP CLI workflows rather than the higher-level guid"
  },
  {
    "path": "optional-skills/mcp/fastmcp/scripts/scaffold_fastmcp.py",
    "chars": 2005,
    "preview": "#!/usr/bin/env python3\n\"\"\"Copy a FastMCP starter template into a working file.\"\"\"\n\nfrom __future__ import annotations\n\ni"
  },
  {
    "path": "optional-skills/mcp/fastmcp/templates/api_wrapper.py",
    "chars": 1608,
    "preview": "from __future__ import annotations\n\nimport os\nfrom typing import Any\n\nimport httpx\nfrom fastmcp import FastMCP\n\n\nmcp = F"
  },
  {
    "path": "optional-skills/mcp/fastmcp/templates/database_server.py",
    "chars": 2238,
    "preview": "from __future__ import annotations\n\nimport os\nimport re\nimport sqlite3\nfrom typing import Any\n\nfrom fastmcp import FastM"
  },
  {
    "path": "optional-skills/mcp/fastmcp/templates/file_processor.py",
    "chars": 1663,
    "preview": "from __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import Any\n\nfrom fastmcp import FastMCP\n\n\nmcp ="
  },
  {
    "path": "optional-skills/migration/DESCRIPTION.md",
    "chars": 117,
    "preview": "Optional migration workflows for importing user state and customizations from\nother agent systems into Hermes Agent.\n"
  },
  {
    "path": "optional-skills/migration/openclaw-migration/SKILL.md",
    "chars": 15889,
    "preview": "---\nname: openclaw-migration\ndescription: Migrate a user's OpenClaw customization footprint into Hermes Agent. Imports H"
  },
  {
    "path": "optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py",
    "chars": 63213,
    "preview": "#!/usr/bin/env python3\n\"\"\"OpenClaw -> Hermes migration helper.\n\nThis script migrates the parts of an OpenClaw user footp"
  }
]

// ... and 864 more files (download for full content)

About this extraction

This page contains the full source code of the NousResearch/hermes-agent GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1064 files (15.9 MB), approximately 4.2M tokens, and a symbol index with 11130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!