Skip to main content

Agent IR and Profile Ferry

An experimental, internal Python package for converting custom agent definitions through a typed intermediate representation. It is not a proposed universal agent specification.

This slice implements Claude Code and OpenCode V2 Markdown source adapters and a Codex custom-agent TOML target adapter. The adapters preserve prompt and source metadata, and conversion diagnostics distinguish lost functionality from changes to authority.

See plan.md for the accepted implementation plan and docs/architecture.md for the design.

Development

uv sync --extra dev
uv run pytest
uv run agent-ir schema
uv run agent-ir convert path/to/agent.md \
  --codex-capability filesystem.read_content \
  --codex-filesystem read-only \
  --codex-shell none \
  --codex-network none \
  --codex-delegation none \
  --output /tmp/agent.toml

Codex custom-agent files do not encode an arbitrary per-agent tool allowlist, but they can carry supported per-agent sandbox and feature settings. This adapter maps read-only/workspace-write filesystem boundaries to sandbox_mode, disables the shell tool when it is absent from an explicit Claude allowlist, and disables Codex web search when Claude search is not allowed. These controls cover only the target features they name. Conversion also accepts an operator description of the effective Codex capability boundary through --codex-capability and the filesystem, shell, network, and delegation options. The converter does not inspect or verify the active Codex configuration; its JSON report labels this input as an operator declaration. Only use it after checking the policy actually enforced by the runtime. Omit --codex-capability or pass unknown for any boundary dimension you cannot establish; unknown or broader target authority blocks output.

The JSON report includes an advisory profile template based on the source filesystem grants. --codex-profile-name selects its suggested $CODEX_HOME/<name>.config.toml name. A recommended Codex profile applies to the whole CLI session, including subagents; per-agent sandbox and supported feature settings are emitted in the generated agent TOML. Repository config or runtime flags may override profile settings. Treat the profile as a session-level sandbox aid, inspect the effective configuration, and use a separately constrained OS account, container, or VM when strict isolation is required. The converter never writes this recommendation into the generated subagent TOML or installs it into $CODEX_HOME.

Local agent distributions

The profileferry command can list, inspect, preview, and install an agent from a local or public GitHub distribution. The agents command remains as a compatibility alias. A distribution has an agents.yaml manifest with stable IDs and relative source paths. It accepts Claude Code or OpenCode V2 Markdown sources and installs Codex standalone agent TOML files:

schema_version: 1
name: my-agents
version: 1.0.0
description: Reusable local agent profiles
agents:
  - id: security-auditor
    source:
      harness: claude-code
      path: categories/security/security-auditor.md
profileferry list ./my-agents
profileferry inspect ./my-agents security-auditor
profileferry add ./my-agents security-auditor --to codex --scope project \
  --codex-context ./codex-context.json --dry-run
profileferry add ./my-agents security-auditor --to codex --scope project \
  --codex-context ./codex-context.json
profileferry list github:OWNER/REPO@main
profileferry add github:OWNER/REPO@main security-auditor --to codex --scope user \
  --codex-context ./codex-context.json --dry-run

OpenCode V2 agent identity comes from the path relative to an OpenCode agents directory, while source.path remains the contained file path within the distribution. Manifests therefore require a separate source.agent_relative_path for OpenCode entries:

agents:
  - id: code-reviewer
    source:
      harness: opencode-v2
      path: agents/team/reviewer.md
      agent_relative_path: team/reviewer.md

Standalone conversion uses --from opencode-v2 --source-agent-path team/reviewer.md. The adapter reads V2 Markdown frontmatter and body as data, preserves the original document and ordered permission rules, and retains unknown fields with blocking diagnostics. It supports the documented description, model, mode, and permissions fields, but blocks features Codex cannot preserve, including primary/all modes, disabled agents, step limits, lifecycle behavior, model variants, and resource-specific permission patterns. hidden: true emits a non-blocking diagnostic because Codex agent files cannot preserve OpenCode's hidden-listing and subagent-catalog behavior. A nested OpenCode identity is preserved; installation refuses it when it cannot be represented by Codex's safe single-file agent name.

OpenCode's effective permissions can depend on global rules and runtime context. Inspection does not claim that agent-local rules are the complete policy. Conversion therefore requires either a final agent-local action: "*", resource: "*", effect: deny rule that establishes zero authority, or an explicit source context supplied with --opencode-context. That JSON must contain a complete, strict effective_capabilities object using supported OpenCode action names and consistent Agent IR dimensions; it is an operator declaration, not independently verified evidence. Known local denials cannot be overridden by the context. Ask rules, unknown actions, and scoped resource rules remain blockers because a broad declaration cannot prove their narrower semantics.

For example, an operator who has verified that the active OpenCode policy permits only read, glob, and grep within /workspace can supply:

{
  "effective_capabilities": {
    "tools": [
      {"name": "read", "state": "allowed", "semantic_capabilities": ["filesystem.read_content"]},
      {"name": "glob", "state": "allowed", "semantic_capabilities": ["filesystem.list_paths"]},
      {"name": "grep", "state": "allowed", "semantic_capabilities": ["filesystem.search_content"]}
    ],
    "tool_policy_mode": "allowlist",
    "filesystem": "read-only",
    "shell": "none",
    "network": "none",
    "delegation": "none",
    "workspace_scope": "/workspace"
  }
}

OpenCode path-scoped filesystem or shell access is emitted only when source and target contexts provide the same explicit workspace_scope. For standalone conversions, --codex-workspace-scope supplies the target scope. Use --codex-no-capabilities to declare an explicitly empty target tool boundary; omitting both it and --codex-capability leaves that boundary unknown. OpenCode-derived zero authority can only convert when the Codex context is explicitly no broader. For example, a final catch-all deny can be converted with --codex-no-capabilities and all four Codex access dimensions set to none.

agent-ir convert path/to/zero-authority.md --from opencode-v2 \
  --source-agent-path reviewer.md \
  --codex-no-capabilities \
  --codex-filesystem none --codex-shell none \
  --codex-network none --codex-delegation none \
  --output /tmp/reviewer.toml

The package is named profileferry and provides the matching command, so once it is published the CLI can run ephemerally as uvx profileferry <command>. It is not published to PyPI yet. For development, use uv run profileferry <command>.

The context JSON must validate as CodexTargetContext and describe the effective Codex capability boundary enforced outside the generated agent file. The CLI cannot verify that policy; it labels this input as operator-declared and unverified. The Codex renderer blocks output when it cannot prove that the effective target is no broader than the source. Project installs go to .codex/agents/; user installs go to $CODEX_HOME/agents/ or ~/.codex/agents/. Existing files are preserved unless --force is supplied. Local distribution files are treated as data: commands do not execute scripts, import modules, install dependencies, or access the network.

For example, an operator who has verified these effective limits can supply:

{
  "enforced_capabilities": [
    "filesystem.read_content",
    "filesystem.search_content",
    "filesystem.list_paths"
  ],
  "filesystem": "read-only",
  "shell": "none",
  "network": "none",
  "delegation": "none",
  "workspace_scope": "workspace"
}

GitHub distribution locators use github:OWNER/REPO@REF. Mutable refs are resolved to a full commit SHA before downloading; CLI output shows the requested ref, resolved SHA, and whether the immutable cache was used. You can pin a full SHA directly. Full-SHA cache hits work offline. Set PROFILEFERRY_CACHE_DIR to change the cache location; the legacy AGENT_IR_CACHE_DIR setting remains supported. By default the cache uses $XDG_CACHE_HOME/profileferry/distributions or ~/.cache/profileferry/distributions.

Remote support currently accepts public GitHub repositories only and does not use credentials. Archives are size-limited and extracted as data; traversal, symlinks, hard links, special files, and ambiguous paths are rejected. This protects extraction and repeatability, but a commit pin does not establish that an upstream agent is trustworthy. Private repositories, other Git hosts, submodules, automatic updates, and cache cleanup commands are not supported yet.

Publishing

The package is configured for PyPI Trusted Publishing from GitHub Actions. The release workflow builds and checks the package in one job, then publishes only the built artifacts in a separate OIDC-enabled job using the GitHub pypi environment. No PyPI token is stored in repository secrets. To publish a release, first merge the workflow to the default branch, configure a PyPI Trusted Publisher for luluthehungrycat/profileferry with workflow .github/workflows/publish.yml and environment pypi, then publish a GitHub Release. The first PyPI upload has not been made yet.

Metadata

Release files for profileferry 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for profileferry 0.1.0
File Size Uploaded Provenance Actions
profileferry-0.1.0.tar.gz 66.5 kB With provenance Details for profileferry-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for profileferry 0.1.0
File Interpreter ABI Platform Actions
profileferry-0.1.0-py3-none-any.whl Python 3 none any Details for profileferry-0.1.0-py3-none-any.whl

Total release size: 102.5 kB

Release files / profileferry-0.1.0.tar.gz

Download URL profileferry-0.1.0.tar.gz
Size 66.5 kB
Tags Source
SHA-256 checksum
How to use checksums
58544d617b76bfd7bcd08f65c1eb8112b57d7fbb5bfaac077a344efc4d483dd7
BLAKE2b-256 checksum
How to use checksums
a1a2aa233f88a7f3f80d8027c674d5122acd9b1044fd98ff7b59641d91af5df6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / profileferry-0.1.0-py3-none-any.whl

Download URL profileferry-0.1.0-py3-none-any.whl
Size 36.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7c394d955f1773b19ede123d452b792c082141eb08a0ee00d0fa136115fb634e
BLAKE2b-256 checksum
How to use checksums
ae037f387de5fd0042b4453a3298527c78a6f6b9b98c2be594d4b38413db9798
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Security for profileferry 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for profileferry-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page