DEV Community

Cover image for I wanted a signed AIMarket provider without the monorepo. One CLI later, I had one.
Alex
Alex

Posted on Originally published at alexar76.github.io

I wanted a signed AIMarket provider without the monorepo. One CLI later, I had one.

Empty folder becoming a stamped PROVIDER box on a dark desk

Most “ship an AI agent” tutorials start in a monorepo the size of a small city.

I wanted the opposite: an empty directory, one command, and a provider that already knew how to sign its answers.

That tool now exists on PyPI as create-aimarket-agent 0.1.0 — a standalone scaffolder for AIMarket Protocol v2 capability providers. Docs in five languages. Generator CI claims 47 tests and 100% branch coverage. MIT.

It does not publish you to a marketplace. That part stays manual on purpose.


One command. Stable names.

Dark terminal running uvx create-aimarket-agent

uvx create-aimarket-agent my-agent --kind tool --metis
cd my-agent
uv sync --extra dev
uv run python configure_provider.py
uv run pytest
uv run python validate_manifest.py
uv run python agent.py
Enter fullscreen mode Exit fullscreen mode

The CLI, flags, filenames, and API fields stay English everywhere. Localization covers the docs, not the contract.

Kinds:

create-aimarket-agent my-tool --kind tool
create-aimarket-agent my-data --kind data-provider
create-aimarket-agent my-orchestrator --kind orchestrator
create-aimarket-agent my-agent --no-metis
Enter fullscreen mode Exit fullscreen mode

Scaffolding is atomic: stage in a temp folder, rename into place, clean up. A failed copy leaves no half-written repo.

Project names are allow-listed before they touch source or JSON. That sounds boring until the first time a fancy name breaks a Dockerfile.


What lands on disk

Path Job
agent.py FastAPI /health + /invoke
capability.json Protocol v2 manifest
provider_signing.py Persistent Ed25519 identity + request-bound signatures
configure_provider.py Writes provider_pubkey into the manifest
validate_manifest.py Fail-closed checks before you even think about publish
test_agent.py API, signature, request-size tests
Dockerfile Non-root image, key volume at /data
.github/workflows/test.yml CI for the generated project

The default manifest looks like this (placeholders replaced with your slug):

{
  "product_id": "my-agent",
  "capability_id": "my-agent.invoke@v1",
  "name": "my-agent",
  "description": "A generated AIMarket tool capability",
  "invoke_url": "http://127.0.0.1:8080/invoke",
  "publisher_id": "community",
  "provider_pubkey": "",
  "price_per_call_usd": 0.001,
  "input_schema": {"type": "object", "additionalProperties": true},
  "output_schema": {"type": "object", "additionalProperties": true},
  "verification": {"metis": true}
}
Enter fullscreen mode Exit fullscreen mode

capability.json beside a VALIDATE stamp

configure_provider.py fills provider_pubkey. validate_manifest.py rejects duplicate JSON keys, broken Ed25519 keys, non-finite prices, bad URLs, and public HTTP endpoints before you ship.

The skeleton /invoke only accepts the product and capability ids declared in its own manifest. An untrusted caller cannot make your key sign someone else’s identity.


The part I actually cared about: the stamp

Steel stamps ATOMIC, Ed25519, REQUEST-BOUND beside a provider.key fob

Pretty agents are easy. Replay-resistant receipts are not.

The generated signer keeps a 32-byte Ed25519 seed at mode 0600. Symlinks and non-regular files get rejected. Responses sign a canonical envelope with:

  • capability_id
  • product_id
  • SHA-256 of the input
  • the result

That binding is the point. Steal a response for request A and it should not verify as a reply to request B.

The signature is a receipt for what this provider returned. It is not an authorization gate for random internet callers. Keep 8080 on loopback until real HTTPS ingress, concurrency, and rate limits sit in front.


Docker without drama

Read-only crate labeled 8080 with a /data volume

docker build -t my-agent .
docker run --read-only --tmpfs /tmp \
  -p 127.0.0.1:8080:8080 \
  -v my-agent-key:/data my-agent
Enter fullscreen mode Exit fullscreen mode

Runs as UID 65532, health-checks /health, stores the key under /data/provider.key.

Back up that key before you register anything. Rotate it and the Hub’s stored provider_pubkey becomes a stranger.


From skeleton to something useful: THEMIS

THEMIS dossier with APPROVE / REVIEW / REJECT

The README does not stop at “echo hello.” There is a full tutorial that turns the scaffold into THEMIS — a procurement gate that answers:

Should this candidate agent be approved, sent to human review, or rejected?

Deterministic policy checks. Projected cost. OWASP Agentic risk mappings. Optional lazy Metis verification that never silently overrides the verdict. Same request-bound signing model.

If you only want a tool capability, ignore THEMIS. If you want a real admission gate, start there.


What this is not

  • Not auto-publish. Stake, publisher identity, trust policy, and Hub registration stay explicit operator steps.
  • Not “the monorepo.” The GitHub repo is a read-only mirror of the AI-Factory tree. PRs there get overwritten on the next sync — open an issue instead.
  • Not a substitute for HTTPS. Loopback + signature ≠ production exposure.

Try it

uvx create-aimarket-agent my-agent --kind tool --metis
Enter fullscreen mode Exit fullscreen mode

Empty folder in. Signed provider out. Marketplace listing still your call — which is exactly how I wanted it.

Top comments (0)