qory

command module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

🐝 qory

A harness is what your coding agent reads before it works: instructions, skills, agents, commands and settings. qory builds the harness of a repository from modules. Each module is one piece of harness: the piece every repository of your team shares, the piece for apps built on your framework, the piece this repository alone needs. The repository commits the list of modules, not a copy of their files.

The problem

Your coding agent reads its harness from the repository. You have many repositories, and more than one agent. Claude Code reads .claude, Codex reads .codex, Gemini CLI reads .gemini. So every repository holds one copy per tool. When the shared part improves, the copies drift, and nothing tells you which version a checkout runs with.

Don't worry, use Qory

What qory does

A stack lists modules in order: the harness your team shares, the harness for apps built on the framework, the harness this repository alone needs. qory composes them into one tree and writes that tree the way each tool reads it. The tree is linked into the checkout and kept out of git. A report says which module every entry came from.

A module is written once and serves Claude Code, Codex CLI, Gemini CLI, OpenCode, Cursor, GitHub Copilot CLI, Amp, Goose, and every tool that reads AGENTS.md and .agents/skills. A checkout can serve two tools at once, and both read the same harness.

The model is Docker's: a module is an image, the stack is the Compose file, and the composed tree is what runs. One rule differs on purpose. When two modules provide the same entry, qory refuses to compose until the stack says which one to keep. Nothing wins by coming last.

Three steps

  1. Write qory.yaml in the repository, or let qory setup repo write it:

    apiVersion: qory.dev/v1alpha1
    harness:
      target:
        runtime: claude          # or both at once: [claude, codex]
        model: opus
      modules:
        - name: core             # what every repository of yours gets, pinned to a tag
          source: {git: https://github.com/acme/harness, ref: v2.4.0, path: core}
        - name: nextjs           # the harness for Next.js apps
          source: {path: ../harness/nextjs}
        - name: app              # this repository's own, committed with it
          source: {path: ./harness}
    

    Every module has a qory-module.yaml that names it. A repository can also take a stack someone else delivers, a qory-stack.yaml: it names that stack under extends, says what it composes it for under target, and adds its own modules. The delivered modules cannot be changed. A runner that holds the stack tree names the base with qory harness compose -f <stack> instead, and the repository's file then names no version, ref or URL of it. The harness repository states the qory it needs once, in its own qory.yaml, qory: ">=0.5.0", and every stack it delivers is held to it.

    apiVersion: qory.dev/v1alpha1
    harness:
      extends: {git: https://github.com/acme/harness, ref: v2.4.0, stack: nextjs}
      target: {runtime: claude, model: opus}
      modules:
        - name: marketing          # the harness repository exports it too
          source: {git: https://github.com/acme/harness, ref: v2.4.0, module: marketing}
        - name: app
          source: {path: ./harness}
    

    stack and module name what the harness repository publishes in the exports section of its own qory.yaml, so nobody outside it depends on its directories:

    exports:
      dir: ./harness               # where stacks/ and modules/ are; default: the root
      stacks: [nextjs]
      modules: [core, nextjs, marketing]
    
  2. Compose it:

    qory harness compose
    
  3. Start your agent in the checkout. It reads the composed tree through the files it already looks for.

Install

With Homebrew:

brew install qoryai/tap/qory

With the install script, which puts the release binary in ~/.local/bin:

curl -fsSL https://raw.githubusercontent.com/qoryai/qory/main/install.sh | sh

From source, with Go 1.27 or later:

go install github.com/qoryai/qory@latest

To update, run qory update: it installs the newest release the way this qory was installed, with brew, with go install, or by replacing the release binary in place after checking it against the release's checksums. Every command looks for a newer release when it runs on a terminal, asking GitHub at most once an hour, and says so after its output when the newest release is ahead of its version. A build from main between releases is told only when it is behind a release. Set QORY_NO_UPDATE_CHECK=1 to turn that off; it is off when CI is set.

Try it

mkdir hello && cd hello
qory setup example       # writes a stack and two modules
qory harness compose     # or: qory hc; for Codex: qory hc --runtime codex
claude                   # type /hello
qory harness remove      # or: qory hr

Both modules ship a greet skill. The README that qory setup example writes says which line to delete to watch qory refuse the collision. For a real repository, run qory setup repo instead.

Worktrees

One branch per worktree, beside the main checkout. qory prepares the worktree the way the repository's qory.yaml says and composes the harness into it:

# qory.yaml, committed, beside the harness section
apiVersion: qory.dev/v1alpha1
worktree:
  base: main                     # the base branch, read on the remote; default: the remote's HEAD
  link: [.env, .env.local]       # linked from the main checkout into the worktree
  run:
    add: [pnpm install]          # run in the new worktree
qory worktree add feature        # ../wt-feature on branch feature, pushing to origin/feature
qory worktree add feature --base v1.2.0   # a branch that exists is moved onto the base, after a question
qory worktree add feature --offline       # without the fetch every add starts with
qory worktree add --branch feature   # attach to the remote's feature: fetched, tracked, refused when the remote lacks it
qory worktree add --pr 7             # attach to pull request 7: its branch, found among the remote's refs
qory worktree add review --pr 7      # the same, in ../wt-review
qory worktree remove             # the worktree you stand in, and its branch
qory setup shell                 # make your shell cd into a new worktree, and back on remove

Every add fetches the remote first, so a new branch starts at the remote's tip and a branch pushed from another machine is tracked instead of cut anew; a fetch that fails stops the add, and --offline goes on with the refs already there. The branch goes with the worktree on remove: quietly when every commit of it is on the remote, in the main checkout or on the base it was cut from, and after a question otherwise, with push, keep, delete anyway and stop as the answers. --keep-branch keeps it, and worktree.branch: keep makes that the default. -v on either verb prints each git command as it runs, and what the configured commands print.

--pr asks no hosting API: the pull request's head is one of the refs the remote publishes, refs/pull/<n>/head on GitHub and Forgejo, refs/merge-requests/<n>/head on GitLab, refs/pull-requests/<n>/from on Bitbucket Server. A host with another layout names it as worktree.pr: refs/.../{n}/... in the repository's qory.yaml; one that publishes none, Bitbucket Cloud, takes --branch with the pull request's branch.

Where a worktree goes and what it is called is your choice, not the repository's: worktree.dir, worktree.name and worktree.branch in your own qory.yaml, which qory setup machine writes. A file the repository must not name goes there too:

# ~/.config/qory/qory.yaml
worktree:
  link: [{from: ~/secrets/app.env, to: .env}]   # this machine's path, linked as .env

Commands

qory setup repo          # write the repository's qory.yaml: its stack, module and worktree settings
qory setup example       # write the hello example into the current directory
qory setup machine       # write your qory.yaml in ~/.config/qory: how qory runs here
qory setup shell         # completions, and a shell that follows worktree add and remove
qory harness compose     # compose the stack into the checkout you stand in   (qory hc)
qory harness inspect     # the report: every entry and the module it came from (qory hi)
qory harness remove      # remove the composed tree and its links               (qory hr)
qory harness launch      # the command that starts a runtime on the tree          (qory hl)
qory run                 # start a runtime on the tree, observed and recorded
qory worktree add        # add a worktree for a branch and prepare it            (qory wa)
qory worktree remove     # remove a worktree and its branch                      (qory wr)
qory worktree list       # every worktree with its branch                        (qory wl)
qory config              # every setting, its value and the file it came from
qory update              # install the newest release; --check only says whether one exists

Flags worth knowing on compose:

-f <stack>             compose this stack; as the base of the repository's document when that extends one
--dry-run              print the report and write nothing
--runtime claude,codex render for these runtimes instead of the document's
--model opus           write this model instead of the document's
--force                replace a tracked, unmodified file where a link goes
--update               fetch every git source again
--check                exit 6 when the composed tree is behind the stack and modules
--home <dir>           compose under a directory outside the checkout, and write nothing into it
--no-links             keep the checkout untouched; the tree goes under .qory

Two qory.yaml files are read. Every setting has a default, so both are optional. The repository's file is committed and holds what the repository decides: its stack, and what a worktree needs; it may be named harness.yaml instead, for a file that says nothing of the tool that reads it. Your file, in ~/.config/qory, holds how qory runs on this machine for every repository: the runtime to compose for, where worktrees go, the git timeout. The repository's file is read on top of yours. qory config shows every setting and the file it came from. The reference, one page per command, is under docs/commands.

What you get

  • The files your tool reads, linked to a composed tree under .qory and kept out of git. Your own settings.local.json stays yours.
  • Settings merged from every module, in the tool's own format. Permission lists and hooks join. A value set twice to different things is a collision, never a silent override.
  • MCP servers, one file each in a module, written where every tool reads them.
  • Modules from a directory beside the repository, or from a git repository at a tag, pinned by commit in the report. A repository that publishes stacks and modules lists them under exports, and a consumer names them, never their directories.
  • One instruction file, joined from the modules in order, under the name each tool wants.
  • A report that names the module of every entry. When two modules provide the same entry, a refusal with the lines that resolve it.
  • Part of a module, when that is all you want. exclude leaves entries, the instruction section, settings fragments or variables out. only takes the named things, plus what they need as the module's manifest declares it, and nothing else: only: {skills: [deploy]} is the deploy skill, the command it runs and the agent it calls, from a module full of other things. A required entry you take from another module instead is one exclude line beside the only.

The composed tree

The home, .qory/harness, holds one directory per runtime. Most of what is in it is a symlink to a file in a module: every skill, agent, command and hook. Beside the links are the generated copies: AGENTS.md at the root of the home, the instruction file each runtime wants, its settings and its MCP file, each joined from the modules' fragments.

Editing a module's file is live through the link. Editing a module's instruction section or a settings fragment is not: the merged copy is generated, and it stays as it was until the next compose. qory harness compose --check compares the home with what the stack and modules say now and exits 6 when a merged copy is behind, which is the check a CI job runs.

What the checkout holds is links too. .claude is a real directory with one symlink per entry, so Claude Code's own settings.local.json stays beside them; .mcp.json and a root AGENTS.md are symlinks. A tool that walks the tree has to follow them:

  • find needs -L.
  • grep -R given a symlink as its operand without a trailing slash reads nothing with BSD grep on macOS and follows the link with GNU grep on Linux, and neither reports an error. Give it the directory with the slash, grep -R pattern .claude/, or use -R -L.
  • rg needs --follow.

git status does not show the tree. Every path qory writes is listed in .git/info/exclude, which every worktree of a repository shares.

A home outside the checkout

The tree can live outside the checkout, owned by whatever composes it, a workflow or a launcher, and the checkout then carries nothing of it: no link, no .qory, no exclude line, so a tracked harness/ directory is no collision and a pull is an ordinary pull. --home <dir>, or harness.home in your own qory.yaml, names the directory, and every checkout and worktree gets its own home under it. Each tool takes such a home from its own command line or environment, and qory harness launch prints the command:

qory harness compose --home ~/.cache/qory/homes
eval "$(qory harness launch --runtime claude --home ~/.cache/qory/homes)"

For Claude Code that is --plugin-dir for a plugin the compose renders, --settings for the permissions, hooks, environment and model, --mcp-config for the servers, --append-system-prompt-file for the instructions, and --setting-sources user so no .claude of the checkout or of a directory above it is read. Cursor takes the same plugin, Codex takes the tree as its CODEX_HOME, OpenCode as its OPENCODE_CONFIG_DIR, Copilot the skills and agents through --add-dir, Amp and Gemini their settings file. Every line is the tool's own template, and harness.launch.<runtime> in your qory.yaml changes the command, the arguments or the variables when a tool's flags move. --json prints the same as one object. The contract says what each tool takes from outside and what it still reads from the checkout.

Running a session

qory run starts the runtime on the composed tree inside the session runner of qoryai/runner: the same launch as qory harness launch, with every connection the runtime makes going through a proxy on your machine and recorded, and the session written as events beside its output.

qory run                              # the composed runtime, at your terminal
qory run claude -- -p "Reply pong"    # one headless turn; arguments after -- go to the runtime

The record is .qory/runs/<id>/: events.jsonl, one event per line, and output.log, the session's bytes. One optional file, ~/.config/qory/runner.yaml, says what the runner does on this machine. It lives beside your qory.yaml and nowhere else, so a repository cannot set it:

# ~/.config/qory/runner.yaml
apiVersion: qory.dev/v1alpha1
egress:                  # what the runtime may reach; enforce denies the rest
  mode: enforce          # or observe: record everything, deny nothing
  allow: [api.anthropic.com, "*.github.com"]
webhook:                 # where to post the events as well; optional
  url: https://example.com/qory/events
  secret: sixteen-characters-at-least   # or QORY_WEBHOOK_SECRET in the environment
wall:                    # start the runtime in a container; optional
  adapter: docker
  image: example.com/agent:1            # yours: the runtime and your toolchain
  env: [ANTHROPIC_API_KEY]              # names; nothing else of your environment goes in

A module declares the hosts it reaches under egress in its manifest, and the compose unions them into the report. When the harness declares, the runtime reaches the declared hosts the policy covers and nothing else; the policy is the ceiling. When no module declares, the policy's list stands as it is.

With a webhook configured the runner does not start unless the receiver answers; --local runs with the files alone. A denied connection is recorded and the session goes on; nothing here ends a session. The formats are in the runner's contract.

The proxy sees only programs that honour it. A wall makes the rest fail: with a wall section, or --wall docker --image <image> for one run, the runtime starts in a container on a network with no route out, and reaches the proxy, and nothing else, through a relay. The container sees the checkout and the composed home and nothing else of your machine; the runner, the policy, the record and the webhook's secret stay outside. It needs the docker command and an engine behind it, and an image of yours that holds the runtime; qory builds none. What to know:

  • The model credential goes in by name, wall.env or --env, and is then the agent's. A subscription login kept in a Mac's Keychain does not reach a container; use an API key, or a token from claude setup-token.
  • Inside the container the relay and the hook forwarder are qory's own Linux build. On Linux that is the binary you run. On a Mac, download the Linux archive of the same release for your engine's architecture and name the binary as wall.helper.
  • The container sees the checkout it was started in and no other directory. In a git worktree the repository's data lives in the main checkout, outside it, so git inside the container does not work there yet; a clone works.
  • Behind a wall the proxy reaches your own machine only for a host egress.allow names itself, in either mode, and never the cloud metadata address. For a local model endpoint or MCP server, list your machine's host name, and point the harness at that name: localhost inside the container is the container.
  • With the engine in a virtual machine, as on a Mac, the runtime's hooks do not reach the runner, so a walled run there has no hook events; the log, the egress record and the structured output are there. On a Linux host they cross.

The format

The stack, the module manifest, qory.yaml, the composition rules and the runtimes are specified in contracts/harness/v1, with JSON schemas and the fixtures the test suite runs.

Contributing

See CONTRIBUTING.md. Contributions are made under the agreement in CLA.md.

Licence

Apache License 2.0. See LICENSE. Qory™ is a trademark of 8wonders GmbH; TRADEMARKS.md says what you may do with the name.

Documentation

Overview

Command qory composes the harness a runtime loads from modules.

Directories

Path Synopsis
Package cmd is the command tree of the qory binary: one command per noun, one subcommand per verb.
Package cmd is the command tree of the qory binary: one command per noun, one subcommand per verb.
internal
checkout
Package checkout locates the git checkout a command works on and names it.
Package checkout locates the git checkout a command works on and names it.
compose
Package compose turns a stack into one flat tree: every entry once, from one module.
Package compose turns a stack into one flat tree: every entry once, from one module.
config
Package config reads qory.yaml: the repository's own document and the machine's.
Package config reads qory.yaml: the repository's own document and the machine's.
exports
Package exports reads the exports section of a repository's qory.yaml: the stacks and the modules the repository publishes for other repositories, each by name, and the directories they live in.
Package exports reads the exports section of a repository's qory.yaml: the stacks and the modules the repository publishes for other repositories, each by name, and the directories they live in.
gendocs command
Command gendocs writes the command reference, one Markdown page per command, into the directory given as its argument.
Command gendocs writes the command reference, one Markdown page per command, into the directory given as its argument.
module
Package module reads one directory of harness material: its optional manifest, the entries it ships per kind, its settings fragments and its instruction file.
Package module reads one directory of harness material: its optional manifest, the entries it ships per kind, its settings fragments and its instruction file.
render
Package render writes a composed harness into a checkout the way one runtime reads it.
Package render writes a composed harness into a checkout the way one runtime reads it.
render/amp
Package amp renders for Amp, which reads a project's .amp directory for its settings, AGENTS.md at the checkout root, and skills from .agents/skills.
Package amp renders for Amp, which reads a project's .amp directory for its settings, AGENTS.md at the checkout root, and skills from .agents/skills.
render/any
Package any renders for a program that follows the AGENTS.md convention and has no package of its own, reading the instructions from AGENTS.md at the checkout root and skills from .agents/skills.
Package any renders for a program that follows the AGENTS.md convention and has no package of its own, reading the instructions from AGENTS.md at the checkout root and skills from .agents/skills.
render/claude
Package claude renders for Claude Code, which reads a project's .claude directory: the skills, agents, commands, output styles and hooks under it, its settings.json, and CLAUDE.md beside them, and .mcp.json at the checkout root for the MCP servers.
Package claude renders for Claude Code, which reads a project's .claude directory: the skills, agents, commands, output styles and hooks under it, its settings.json, and CLAUDE.md beside them, and .mcp.json at the checkout root for the MCP servers.
render/codex
Package codex renders for Codex CLI, which reads a project's .codex directory, skills from .agents/skills, and AGENTS.override.md ahead of AGENTS.md.
Package codex renders for Codex CLI, which reads a project's .codex directory, skills from .agents/skills, and AGENTS.override.md ahead of AGENTS.md.
render/copilot
Package copilot renders for GitHub Copilot CLI, which reads agents from .github/agents, hook files from .github/hooks, AGENTS.md at the checkout root, and skills from .agents/skills.
Package copilot renders for GitHub Copilot CLI, which reads agents from .github/agents, hook files from .github/hooks, AGENTS.md at the checkout root, and skills from .agents/skills.
render/cursor
Package cursor renders for Cursor and the Cursor CLI, which read a project's .cursor directory for agents, hooks and settings files such as hooks.json, cli.json and mcp.json, AGENTS.md at the checkout root, and skills from .agents/skills.
Package cursor renders for Cursor and the Cursor CLI, which read a project's .cursor directory for agents, hooks and settings files such as hooks.json, cli.json and mcp.json, AGENTS.md at the checkout root, and skills from .agents/skills.
render/gemini
Package gemini renders for Gemini CLI, which reads a project's .gemini directory, holding its settings.json and the skills, agents, commands and hooks under it, and GEMINI.md at the checkout root.
Package gemini renders for Gemini CLI, which reads a project's .gemini directory, holding its settings.json and the skills, agents, commands and hooks under it, and GEMINI.md at the checkout root.
render/goose
Package goose renders for Goose, which reads AGENTS.md at the checkout root, skills from .agents/skills and agents from .agents/agents.
Package goose renders for Goose, which reads AGENTS.md at the checkout root, skills from .agents/skills and agents from .agents/agents.
render/opencode
Package opencode renders for OpenCode, which reads a project's .opencode directory for agents, commands and hooks, opencode.json for its config, AGENTS.md at the checkout root, and skills from .agents/skills.
Package opencode renders for OpenCode, which reads a project's .opencode directory for agents, commands and hooks, opencode.json for its config, AGENTS.md at the checkout root, and skills from .agents/skills.
report
Package report is the JSON record of one compose: every module with its pin, every entry with its module.
Package report is the JSON record of one compose: every module with its pin, every entry with its module.
source
Package source resolves a module's source to a directory on disk and a pin.
Package source resolves a module's source to a directory on disk and a pin.
stack
Package stack reads qory-stack.yaml.
Package stack reads qory-stack.yaml.
ui
Package ui prints everything a person reads on the terminal, in one vocabulary.
Package ui prints everything a person reads on the terminal, in one vocabulary.
update
Package update finds the newest release of qory and installs it.
Package update finds the newest release of qory and installs it.
user
Package user reads the name to greet a person with from the local configuration.
Package user reads the name to greet a person with from the local configuration.
worktree
Package worktree adds, removes and lists the linked worktrees of a repository, and prepares a new one the way the repository's qory.yaml says: files linked or copied from the main checkout, or from anywhere on the machine, and commands run in the new worktree.
Package worktree adds, removes and lists the linked worktrees of a repository, and prepares a new one the way the repository's qory.yaml says: files linked or copied from the main checkout, or from anywhere on the machine, and commands run in the new worktree.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL