The miniest self-improving agent harness.
As of plan 018 (R6): agents now run with
restrictedpermissions by default — read+MCP only, no shell/write/network. Existing installs are grandfathered via sidecarlockedDefault. Override per-run with--permissions yolo(legacy posture) or per-agent withpermissions:frontmatter. Seedocs/how/permissions.md.
Define AI agents as folders containing prompt.md + optional schemas + instructions, then run them against @github/copilot-sdk. Every agent produces structured retrospective feedback — what worked, what was confusing, and a magic wand wish for what should change. This feedback loop makes both the agents and the harness better over time.
For long-running or multi-agent workflows, minih also supports coordination-aware agents: opt-in agents with an outside peer contract, run-scoped inbox/state files, outside CLI commands, and inside MCP tools for progress handoffs. Start with AGENTS_README.md#coordination-aware-agents, then use docs/how/coordination-loop-validator.md for the canonical rich worked example.
export GH_TOKEN=$(gh auth token)
npx github:AI-Substrate/minih quickstartOne command — scaffolds a hello-world agent, runs it, shows the results. Zero to success in 60 seconds.
Each run's retro lands in
docs/retros/<slug>.md(review before commit). See AGENTS_README.md § The Improvement Loop.
Pin a specific version:
npx github:AI-Substrate/minih#v0.x.y quickstart
# Latest (HEAD)
npm install github:AI-Substrate/minih
# Or pin a release
npm install github:AI-Substrate/minih#v0.x.y
# minih uses @github/copilot-sdk as a peer dependency
npm install @github/copilot-sdknpx minih init my-agentThis creates:
agents/my-agent/
├── prompt.md # Your agent's prompt (with YAML frontmatter)
├── output-schema.json # JSON Schema for structured output
└── instructions.md # Agent identity and rules
Open agents/my-agent/prompt.md and write what your agent should do:
---
description: "Analyze code for common security issues"
tags: [security, review]
---
# Security Scan
## Objective
Scan the project for common security issues...export GH_TOKEN=your-github-token
npx minih run my-agentThe agent executes, produces JSON output (with a self-improving retrospective), and stores everything in agents/my-agent/runs/<timestamp>/.
npx minih last-run my-agent # Show latest run path
npx minih history my-agent # List all past runs
npx minih validate my-agent # Re-validate latest output against current schemaDon't hand-copy agents between projects. Install them instead:
minih agent install code-review-companion # from the bundled registry
minih agent install github:owner/repo#main:agents/x # from any public GitHub repo
minih agent install /path/to/local/agent-folder # from a local cloneEach install copies the agent's manifest-listed files into agents/<slug>/, writes a provenance sidecar (.minih-source.json), and is idempotent — re-running upgrades from upstream and atomic-swaps changed files while preserving runtime data (runs/, inbox/, state/).
Browse the bundled catalog:
minih agent list --available # what you can install by slug
minih agent list # what's already installed (with source-type column)
minih agent info <slug> # provenance, manifest, per-file drift statusFull surface — manifest format, security model, error reference, curation — in docs/how/agent-pack.md.
agents/
├── _shared/
│ └── preamble.md # Shared preamble prepended to every agent
├── my-agent/
│ ├── prompt.md # Required — agent prompt with YAML frontmatter
│ ├── output-schema.json # Optional — JSON Schema 2020-12 for output
│ ├── input-schema.json # Optional — JSON Schema for input params
│ ├── instructions.md # Optional — agent identity and rules
│ └── runs/ # Auto-created — run artifacts (gitignored)
│ └── 2026-04-05T07-30-00-000Z/
│ ├── events.ndjson
│ ├── completed.json
│ └── output/
│ └── report.json
Every prompt.md must have YAML frontmatter with at least a description:
---
description: "What this agent does — shown in `minih list`"
tags: [optional, categories]
---Every agent must produce JSON output containing summary and retrospective fields. These are enforced by the runner regardless of whether you define an output schema. Your agent-specific fields go alongside them.
See src/schemas/system-output.json for the full contract schema.
Minimal valid output:
{
"summary": "Scanned 12 files, found 3 potential issues...",
"retrospective": {
"workedWell": "File discovery was fast and the project structure was clear.",
"confusing": "Wasn't sure if I should scan node_modules or just src/.",
"magicWand": "A MINIH_SCAN_PATHS env var listing which directories to scan would save me from guessing."
}
}The retrospective.magicWand is the most valuable thing an agent produces — it directly improves the system for every agent that runs after it.
Create and run your first agent in one command. No flags, no editing.
minih quickstartScaffolds agents/hello-world/prompt.md (if not exists), runs it, and shows next steps. Idempotent — safe to run multiple times.
Execute an agent.
minih run my-agent
minih run my-agent --model claude-sonnet-4 --timeout 600
minih run my-agent --param file_path=src/main.ts --param depth=3
minih run my-agent --label review-a # Label rows in minih runs list/status
minih run my-agent --dry-run # Preview prompt without executing
minih run my-agent --verbose # Old-style timestamped event log| Flag | Description |
|---|---|
-m, --model <model> |
Model to use (default: claude-opus-4.6, override: MINIH_DEFAULT_MODEL) |
-r, --reasoning <effort> |
Reasoning effort: low, medium, high, xhigh |
-t, --timeout <seconds> |
Wall-clock budget in seconds (default: agent frontmatter or 900) |
--stall-timeout <seconds> |
Inactivity watchdog: fail the run when no provider event arrives for this many seconds; 0 disables (default: 300) |
--max-turns <count> |
Fail the run after this many consolidated assistant messages; 0 = unlimited (default: 0) |
-p, --param <key=value> |
Input parameter (repeatable) |
--mcp-config <path> |
Load MCP servers from a JSON config file |
--skill-source <alias-or-path> |
Load local skills from a source such as .agents, global:agents, or path:<dir> |
--skill <name> |
Load only a named skill from configured sources (repeatable) |
--disable-skill <name> |
Disable/exclude a skill by name (repeatable) |
--no-skills |
Disable .minih.json skills for this invocation |
--label <label> |
Human-readable run label shown by minih runs list/status |
--dry-run |
Preview assembled prompt without executing |
--verbose |
Show all events with timestamps (default: pretty streaming) |
Display modes: By default, minih run shows clean streaming output — thinking in gray italic, tool calls formatted with names, intent changes highlighted. Use --verbose for the timestamped line-per-event log. Non-TTY environments always use verbose mode.
Every run is bounded by three budgets so it always reaches a terminal artifact, even when the provider stream silently dies mid-response (issue #44):
| Budget | Flag (run + resume) | Default | 0 means |
run.json terminalReason |
|---|---|---|---|---|
| Wall-clock | --timeout <seconds> |
agent frontmatter or 900 | invalid (E108) | timeout |
| Inactivity (stall watchdog) | --stall-timeout <seconds> |
300 | disabled | stalled-stream |
| Turns | --max-turns <count> |
0 | unlimited | max-turns |
Semantics:
- Any provider event resets the stall watchdog — text deltas, tool calls, thinking, everything. A stall fires only when the stream goes completely silent for the whole window. The synthetic
run_stalledevent is appended toevents.ndjsonwhen it fires. - A turn is one consolidated assistant message — streaming chunking, tool calls, and thinking never count toward
--max-turns. - All three triggers write
run.jsonstatus: 'failed'+ theterminalReasonabove, writecompleted.json(result: 'timeout'for wall-clock,'failed'for stall/turns), and exit124— cleanup of the SDK subprocess is deadline-bounded and escalates to a force-stop, so a wedged provider can never block the terminal writes. - The effective budgets are recorded in
run.jsonunderbudgets: { timeoutSec, stallTimeoutSec, maxTurns };minih statussurfaces the reason (Reason:line +terminalReasonin the envelope). - Known limitation (tool silence): a tool that legitimately runs longer than the stall window without emitting any event (e.g. a 6-minute build under the default 300s) will trip the watchdog. Raise
--stall-timeoutor pass--stall-timeout 0for such agents. - Windows: detached-run stall behavior is untested on Windows (see issue #44's sibling report copilot-cli#2525); the watchdog logic itself is platform-neutral.
Minih can pass locally installed Copilot/Claude-style skills to SDK-backed sessions without hardcoded absolute paths. Add a small repo config when you want defaults:
{
"skills": {
"sources": [".agents"],
"include": ["minih-test-skill"],
"exclude": []
}
}Or use one-off flags:
minih skills discover
minih skills doctor
minih run test-skills --skill-source .agents --skill minih-test-skillSupported source aliases include .agents / repo:.agents, .claude, .github, global:agents, global:copilot, global:claude, global:pi, and path:<path>. Minih never loads user-global skills implicitly; configure .minih.json or pass flags explicitly.
List available agents with descriptions and required parameters.
minih list # JSON envelope on stdout, table on stderrValidate all agents for convention compliance.
minih doctor # JSON on stdout, human report on stderr
minih doctor --strict # Treat warnings as errorsValidate an explicit file against an agent's output schema.
minih check my-agent --file output.json # Validate specific file
minih check # Best effort inside a run — uses MINIH_OUTPUT_PATH if available
minih check my-agent --file input.json --input # Validate against input schemacheck is for file validation. To validate an already-completed run output, use minih validate <slug> --run <runId>.
Scaffold a new agent folder with templates.
minih init my-agent # prompt + output-schema + instructions
minih init my-agent --with-input # Also create input-schema.json
minih init my-agent --coordinated # Also create outside.md + state schemas
minih init my-agent --no-output # Skip output-schema.jsonList past runs for an agent with timestamps and status. Resumed runs show a ↩ indicator.
Use these when you have multiple runs in flight, especially same-slug runs launched from parallel shells.
minih run worker --label case-a --param id=a &
minih run worker --label case-b --param id=b &
minih runs list --active # active/stale/dead runs across all agents
minih runs list --all --slug worker # bounded history for one slug
minih runs status --run worker/<runId> --run worker/<otherRunId>Rows include the agent slug, run ID, liveness, timestamps, counters, model/session IDs, optional label, and a bounded/redacted params summary. Liveness dead means the run's recorded process no longer exists (plan 025 — formerly misreported as stale); stale is reserved for live-but-quiet runs. Public rows intentionally identify runs by slug + runId; use follow-up commands with explicit --run <runId> rather than inspecting run folders directly.
When more than one active run exists for a slug, single-run commands that default to "latest" refuse with E170 and list candidates. Pass --run <runId> to target one run, or use read-only --latest where the command supports intentionally choosing the newest active run.
Heal run records whose process is gone. A crashed or kill -9'd run leaves run.json claiming active forever; reconcile probes each recorded pid and flips dead ones to status: 'crashed' + terminalReason: 'pid-vanished' (existing diagnoses such as provider-stream-aborted are never overwritten).
minih reconcile worker # heal one agent's runs
minih reconcile worker --run <id> # heal one specific run
minih reconcile --all # heal everything under the agents dir| Flag | Description |
|---|---|
--run <runId> |
Limit to one run id (requires a slug) |
--all |
Reconcile every agent under the agents dir (cannot combine with a slug or --run) |
Lock-guarded — one pass per agents dir at a time (E190 RECONCILE_IN_PROGRESS on contention; stale and dead-owner locks are stolen automatically). Idempotent: a second pass reports nothing to heal. See docs/how/run-liveness.md for the full liveness model.
Send a follow-up message to a completed agent session. The session retains full conversation history — the agent remembers what it did in the original run.
minih resume smoke-test "You didn't validate the test output — check that too"
minih resume code-review --run 2026-04-06T10-04-29-715Z-e94a "Elaborate on the security concern"| Flag | Description |
|---|---|
--run <runId> |
Resume a specific run (default: latest) |
-t, --timeout <seconds> |
Wall-clock budget in seconds (default: agent frontmatter or 900 — shared with run) |
--stall-timeout <seconds> |
Inactivity watchdog; 0 disables (default: 300) |
--max-turns <count> |
Turn budget; 0 = unlimited (default: 0) |
--verbose |
Show all events with timestamps |
System output validation (summary + retrospective) is not enforced on resume — it's a quick follow-up, not a full agent report.
Print a ready-to-paste command to drop into the Copilot CLI with an agent's session history.
minih connect smoke-test # Print command for latest run
minih connect smoke-test --run <id> # Specific run
minih connect smoke-test --list # Show all runs with session IDs| Flag | Description |
|---|---|
--run <runId> |
Connect to a specific run (default: latest) |
--list |
List all runs with their session IDs |
Re-validate the most recent or specified completed run's output against the current schema (useful after updating your schema).
minih validate my-agent # Latest run
minih validate my-agent --run <runId> # Specific completed runPrint the latest run directory and report path.
Follow a running agent's event stream in real time, or print a bounded snapshot.
minih tail my-agent
minih tail my-agent --run <runId> --lines 20 --snapshotIf several active runs share the same slug, pass --run <runId> or inspect candidates first with minih runs list --active --slug <slug>.
| Flag | Description |
|---|---|
--agents-dir <path> |
Agents directory (default: agents) |
-V, --version |
Show version |
The runner sets these during agent execution where the execution environment exposes them. The prompt's literal output path remains authoritative; use minih check <slug> --file <path> if a shell cannot see MINIH_OUTPUT_PATH.
| Variable | Description |
|---|---|
MINIH |
Always 1 — detect you're inside a minih run |
MINIH_AGENT_SLUG |
Current agent slug |
MINIH_RUN_ID |
Unique run identifier (timestamp) |
MINIH_RUN_DIR |
Absolute path to run artifacts folder |
MINIH_OUTPUT_PATH |
Where to write output JSON when available; same target as the literal path in the prompt |
MINIH_AGENTS_DIR |
Absolute path to agents directory |
MINIH_PROJECT_ROOT |
Absolute path to project root |
MINIH_MODEL |
Model being used |
MINIH_TIMEOUT |
Timeout in seconds |
MINIH_SCHEMA_PATH |
Path to output-schema.json (if exists) |
MINIH_INSTRUCTIONS_PATH |
Path to instructions.md (if exists) |
MINIH_PREAMBLE_PATH |
Path to preamble.md (if exists) |
MINIH_HAS_INPUT_SCHEMA |
true if input-schema.json exists, else false |
MINIH_PARAMS |
JSON-encoded input parameters. Values may be of any JSON type — strings, numbers, booleans, objects, arrays — depending on what each schema field declares and how the orchestrator passed them via -p key=value. |
Default model: claude-opus-4.6. Override with MINIH_DEFAULT_MODEL env var or --model flag.
minih uses agents to test and improve itself. These are the best examples of how to write agents:
| Agent | Complexity | What It Demonstrates |
|---|---|---|
hello-world |
Minimal | Just a prompt — the simplest possible agent |
convention-check |
Basic | Output schema, instructions, $ref to retrospective, CLI invocation |
prompt-review |
Intermediate | Input params (--param), cross-agent file reading |
smoke-test |
Advanced | Full CLI lifecycle test (init, doctor, check, dry-run) |
coordination-smoke-test |
Advanced | Minimal primitive check for the outside/inside coordination surface |
coordination-loop-validator |
Advanced | Rich worked example for the three-milestone outside/inside conversation loop |
feedback-digest |
Advanced | Cross-agent aggregation, feedback loop |
self-review |
Complete | Production-grade code review with complex schema |
Start with hello-world, then read through in order. Each agent builds on the concepts introduced by the previous one.
Both Claude Code and Copilot CLI have skills — markdown files that give an LLM a prompt and get a response. You could build most of what minih does as individual skills. So why does minih exist?
In a skill, everything is bundled into a single markdown file. Input parameters are a raw $ARGUMENTS string. Output is whatever the LLM feels like returning. Instructions are mixed into the prompt. Feedback doesn't exist unless you build it yourself. None of it is enforced — it's all implicit, all on the honour system.
minih takes each of those bundled concepts and makes them explicit, separate, enforceable first-class citizens — the same way you'd extract inline code into classes and interfaces:
| Concept | In a skill | In minih |
|---|---|---|
| Input | $ARGUMENTS — a raw string |
input-schema.json — typed, validated before execution |
| Output | Whatever the LLM returns | output-schema.json — AJV-enforced every run |
| Instructions | Mixed into the prompt markdown | instructions.md — separate concern |
| Shared context | Global instructions file (one big blob) | _shared/preamble.md — agent-specific, injected at assembly |
| Feedback | Doesn't exist | Mandatory retrospective — enforced by the runner |
| Run artifacts | Gone when session ends | First-class timestamped folder with events, metadata, output |
Because each concept is its own file, you get composition for free — symlink an output-schema.json across agents that share a contract, $ref into shared schema fragments, or point multiple agents at the same instructions.md. Skills can also reference shared files via relative paths and bundled scripts/ directories, but since skill output isn't validated and input isn't typed, sharing a schema between skills doesn't buy you enforcement. In minih, shared files are shared contracts — the runner actually enforces what it finds.
Once these concepts are decomposed, they become infrastructure. The runner handles prompt assembly, the system output contract enforces retrospectives on every agent, the shared preamble injects project context, and validation runs automatically. When you improve the runner or add a capability like velocity tracking, every agent gets it for free without touching individual files.
Skills are inline code. minih is shared infrastructure.
| Capability | Skills | minih |
|---|---|---|
| Validated output | Freeform text — no enforcement | JSON Schema (AJV) validates every run |
| Run independently | Must run inside a Claude/Copilot session | Standalone CLI — npx minih run from any terminal, CI, or cron |
| Run history | Gone when session ends | Timestamped run folders with history and last-run |
| Event-level observability | Not available | events.ndjson captures every tool call, every message |
| Session resume | Not available | resume sends follow-ups; connect opens interactive sessions |
| Velocity tracking | Not available | Per-agent run-over-run timing with trend indicators |
| Self-improving feedback | Must be built per-skill | Mandatory retrospective with workedWell/confusing/magicWand on every run |
| Difficulty aggregation | Not available | minih difficulties aggregates friction reports across all agents |
| Prompt inspection | Can't see the composed prompt | minih inspect shows exactly what the LLM receives |
| Health checks | Not available | minih doctor validates all agents and harness structure |
| Update propagation | Edit every skill file | Change the runner, preamble, or schema once — all agents inherit |
| Namespace | Every agent is a /slash command in your skills list |
Agents live in agents/ — your skills stay clean for workflow commands |
Skills have real advantages: zero setup (drop a markdown file, it works), conversation context (they see your files, git state, current session), native tools (the host's grep, edit, bash), and orchestration (the LLM can auto-invoke them mid-conversation). Copilot CLI in particular has powerful task orchestration — /fleet decomposes work into parallel subagents, /tasks lets you manage background agents and shell sessions, /plan builds step-by-step execution plans, and /delegate hands off to a coding agent that creates branches and PRs autonomously. That kind of interactive, multi-agent orchestration is where skills shine and minih deliberately stays out of the way.
minih is for repeatable, unattended agent runs that produce structured output. It's the wrong tool for interactive, user-driven work:
- Research and exploration — you need the LLM to ask you clarifying questions, follow up on leads, and adjust direction mid-conversation. That's a skill.
- Planning and architecture — iterative back-and-forth where you refine scope, make trade-offs, and approve decisions. That's a skill.
- Implementation — editing files, running tests, fixing errors in a feedback loop with you watching. That's a skill.
- One-off questions — "how does this module work?" or "what's the best approach here?" Just ask your agent directly.
The rule of thumb: if the task needs your input during execution, use a skill. If the task should run the same way every time and produce a structured report, use minih.
They're not competitors — they work together:
Skills (workflow layer) → /plan, /review, /research, /validate
↓ calls
minih (execution layer) → npx minih run smoke-test
↓ produces
Structured artifacts → report.json + events.ndjson + velocity + difficulties
↓ feeds back into
Skills + preamble + harness → self-improving loop
Skills are great for interactive workflow orchestration. minih is great for repeatable, observable, self-improving agent execution that persists beyond any single session.
The self-improving feedback loop described in the Philosophy section — where agents report friction, you fix it, and the next run is faster — is what makes minih more than a runner. Projects using this loop have seen complex multi-hour tasks compress to minutes over successive iterations, because every agent run leaves the system better than it found it. Skills don't have the infrastructure (run history, velocity tracking, difficulty ledger, mandatory retrospectives) to drive that loop automatically.
All commands (except tail) output a JSON envelope on stdout:
{
"command": "run",
"status": "ok",
"timestamp": "2026-04-05T07:30:00.000Z",
"data": { ... }
}Status values: ok (success), degraded (completed with validation issues), error (failure).
Human-readable formatting goes to stderr (when TTY is detected). Pipe stdout for programmatic consumption.
MIT