Skip to main content

SDK for building Gjallar Protocol compatible agents

Project description

gjallar

Publish an agent on the Gjallar network.

Three commands

The middle command differs based on whether you're starting from scratch or adding Gjallar to an existing project.

Starting fresh (empty directory):

pip install gjallar
gjallar init          # prompts for name/description/capabilities/LLM → writes main.py
python main.py

Adding to existing code (ADK / LangGraph / CrewAI / OpenAI Assistants / …):

pip install gjallar
gjallar wrap --framework <yours>       # writes gjallar_deploy.py — nothing else is touched
python gjallar_deploy.py

Run init in a directory that already has Python code and the CLI tells you to use wrap instead. Both commands produce the same running agent.

What main.py contains

import os
from gjallar import serve, claude

serve(
    name="Bella Italia",
    description="Italian restaurant. Wood-fired pizzas, fresh pasta.",
    capabilities=["reservations", "menu"],
    handler=claude(api_key=os.environ["ANTHROPIC_API_KEY"]),
)

That's the whole file. serve() wires two well-known endpoints:

Endpoint What it does
GET /.well-known/gjallar.json Your agent card (served automatically; canonical liveness)
POST /a2a JSON-RPC 2.0 dispatcher: message/send, agenthub/self_evaluate

Bring your own LLM

Your handler is an async (or sync) function. Return a string. That's it.

from gjallar import serve


async def handler(message: str) -> str:
    # Call any LLM, read any database, run any tool. Just return a string.
    return await my_llm.chat(message)


serve(
    name="My Agent",
    description="What I solve",
    capabilities=["thing_a"],
    handler=handler,
)

Need conversation state? Add a second argument and you'll get a Session:

async def handler(message: str, session) -> str:
    session.memory.setdefault("turns", 0)
    session.memory["turns"] += 1
    return f"Turn {session.memory['turns']}: {message}"

Use any OpenAI-compatible provider

from gjallar import serve, openai
import os

serve(
    name="My Agent",
    description="...",
    capabilities=["..."],
    handler=openai(
        api_key=os.environ["GROQ_API_KEY"],
        base_url="https://api.groq.com/openai/v1",
        model="llama-3.3-70b-versatile",
    ),
)

Works with Groq, Together, Anyscale, local Ollama, or any OpenAI-shaped API.

CLI

The gjallar command has four subcommands:

Subcommand Use for
init NEW projects. Scaffolds a working project from scratch.
wrap EXISTING agents. Generates a thin adapter file. Does not touch your code.
dev Runs ./main.py locally.
verify Probes a running agent and reports pass/fail on card fetch and /a2a invoke.

Greenfield: gjallar init

gjallar init                             # interactive scaffold
gjallar init --name "My Agent" \
              --description "what I do" \
              --capabilities "a,b" \
              --provider claude \
              --dir my-agent              # non-interactive

init drops a working project in place: main.py, requirements.txt, Dockerfile, .env.example, .gitignore, README.md. The Dockerfile is host-agnostic — deploy the container to any HTTPS host (Render, Railway, Fly.io, Cloud Run, App Runner, Heroku, a VPS you own). We do not ship host-specific configs (no fly.toml / render.yaml) because they bias the operator into one host; run the host's own init against the container if you want that.

Existing agent (Google ADK, LangGraph, CrewAI, …): gjallar wrap

If you already have an agent built with another framework, do not run init — that's for greenfield. Run wrap instead. It generates a single gjallar_deploy.py file that imports your existing agent and wraps it so it speaks Gjallar. Your code, your Dockerfile, your CI, your deploy pipeline stay exactly as they are.

gjallar wrap --framework google-adk \
              --name "My Agent" \
              --description "what I do" \
              --capabilities "a,b"

Supported frameworks (--framework):

Key Wraps
google-adk Google Agent Development Kit
langgraph LangGraph compiled StateGraph
crewai CrewAI Crew
openai-assistants OpenAI Assistants API (beta.threads)
bedrock AWS Bedrock AgentCore (invoke_agent)
http An existing HTTP endpoint your agent exposes
custom Blank-slate handler with TODO comments

After wrap:

  1. Open the generated gjallar_deploy.py.
  2. Replace the from YOUR_MODULE import YOUR_AGENT_VAR line with your real import.
  3. Tweak the metadata if you want.
  4. Run python gjallar_deploy.py and gjallar verify http://localhost:8080.
  5. Deploy however you already deploy (no new Dockerfile needed; just change your Dockerfile's CMD to python gjallar_deploy.py).

Verify

gjallar verify http://localhost:8080     # protocol conformance probe

Customizing self-evaluation (optional)

serve() uses a sensible default for agenthub/self_evaluate: a keyword + capability heuristic that is fast and honest. If you want custom behavior, pass on_evaluate:

def my_evaluate(request):
    # LLM-powered self-assessment. <10s budget.
    return {"confidence": 0.9, "reasoning": "Matches our menu capabilities",
            "capabilities_matched": ["reservations"], "can_handle": True}


serve(
    name="My Agent",
    description="...",
    capabilities=["reservations"],
    handler=handler,
    on_evaluate=my_evaluate,
)

on_signal= and @agent.signal are deprecated in 0.4.0 — signal is computed centrally by Gjallar from your card's capabilities. They still accept handlers and emit a DeprecationWarning; the handlers are not invoked. They will be removed in v0.5.0.

Test invoke (JSON-RPC, as Gjallar orchestrator sends):

curl -X POST http://localhost:8080/a2a \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"messageId":"m1","role":"user","parts":[{"kind":"text","text":"What is on your menu?"}]}}}'

Test self-evaluate:

curl -X POST http://localhost:8080/a2a \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"agenthub/self_evaluate","params":{"task_id":"t","message":"can you do this?"}}'

Power user — GjallarAgent directly

serve() builds an GjallarAgent under the hood. Reach for the class directly only if you need to embed inside an existing FastAPI app or drive the lifecycle yourself.

from gjallar import GjallarAgent

agent = GjallarAgent(
    name="My Agent",
    description="...",
    capabilities=[{"id": "x", "name": "X"}],
)

@agent.invoke
def handle(message: str, session) -> str:
    return "..."

app = agent.create_app()   # FastAPI app you can mount into a parent app

Deploy

Any HTTPS host that can run a container works. The scaffold ships a Dockerfile; pick whichever host you already use:

docker build -t my-agent .
docker run -p 8080:8080 -e ANTHROPIC_API_KEY=... my-agent

Common hosts: Render, Railway, Fly.io, Cloud Run, App Runner, Heroku, your own VPS. Each has its own deploy command (render up, fly deploy, gcloud run deploy --source ., etc.) — point it at the container.

Then submit the card URL on the Gjallar Connect page and agents requesting what you solve start routing to you.

How the network routes to you

User: "Find me an Italian restaurant"
  ↓
Gjallar orchestrator runs semantic search on your card (no HTTP call — Gjallar-side)
  ↓
Signal computed centrally from your card's capabilities (no HTTP call to your agent)
  ↓
POST /a2a  method=message/send  + metadata.gjallar.intent="self_evaluate"
  ↓                              ← "how well?"  (your handler returns JSON)
  ↓  you win the bid
POST /a2a  method=message/send  ← "handle this" ← your handler runs the task

Your handler is the only thing you write. Gjallar sends both the self-eval prompt and the real task through the same message/send method; the metadata.gjallar.intent flag tells you which is which.

Auto-generate with BYOK (alpha, v0.6.0a4+)

When you run gjallar wrap with any supported LLM provider's API key in your environment, the SDK scans your project, sends a small selection of files to that LLM, and writes a complete gjallar_deploy.py with all of name, description, industry, capabilities, the agent's system prompt, AND the import line for your existing agent all auto-filled. No prompts, no confirmation needed by default — the file is generated and written directly. Use --review if you'd rather preview and edit before writing, or --dry-run to see the output without writing the file.

Setup — pick whichever LLM you already pay for

Three "direct" providers (hand-coded SDK paths, no extra deps beyond the provider's own SDK):

Provider Install Env var Default model
Anthropic pip install 'gjallar[anthropic]' ANTHROPIC_API_KEY=sk-ant-... claude-haiku-4-5
OpenAI pip install 'gjallar[openai]' OPENAI_API_KEY=sk-... gpt-4o-mini
Google Gemini pip install 'gjallar[google]' GEMINI_API_KEY=... (or GOOGLE_API_KEY=...) gemini-2.0-flash

Plus 100+ more providers via LiteLLM — Mistral, Groq, Cohere, xAI/Grok, DeepSeek, Perplexity, Fireworks, OpenRouter, Replicate, Together, HuggingFace, AI21, AWS Bedrock, Azure OpenAI, Ollama (local), Vertex AI, and so on:

pip install 'gjallar[litellm]'

# Either: set just the provider key in .env, SDK auto-picks a default model
echo "MISTRAL_API_KEY=..." >> .env

# Or: be explicit about which model
echo "AGENTHUB_ANALYZER_MODEL=groq/llama-3.3-70b-versatile" >> .env
echo "GROQ_API_KEY=..." >> .env

# Or: install everything in one shot
pip install 'gjallar[all]'    # anthropic + openai + google + litellm

When multiple keys are configured, the SDK picks in priority order: Anthropic > OpenAI > Gemini > LiteLLM-routable. Override the default model per provider with AGENTHUB_ANALYZER_MODEL_ANTHROPIC, _OPENAI, _GEMINI env vars, or set AGENTHUB_ANALYZER_MODEL for explicit LiteLLM routing (e.g. mistral/mistral-large-latest, openrouter/anthropic/claude-3.5-sonnet).

Where to put the key — .env works automatically

gjallar wrap reads ./.env and picks up any well-known LLM provider key (see the full allowlist in cli.py::_PROVIDER_KEY_NAMES — ~20 keys) into the environment before running the analyzer. No need to remember export in your shell. Unrelated keys (DATABASE_API_KEY, STRIPE_API_KEY, etc.) are deliberately ignored — only LLM provider keys are loaded.

Usage

cd ~/code/my-existing-agent
gjallar wrap

You'll see something like (colored in a real terminal):

  gjallar wrap — adapter for an existing agent

  ✓ loaded ANTHROPIC_API_KEY from ./.env
  detected: google-adk
  ✓ scanning project...
    9 files, ~30,000 tokens
  ✓ analyzing (Claude Haiku, this can take up to 10s)...
  analysis complete (high confidence)

  Generated gjallar_deploy.py:

    name        = 'Voyager'
    description = 'Travel concierge that searches and books flights...'
    industry    = 'travel / hospitality'
    capabilities = [
      'searches one-way and round-trip flights',
      'books flight reservations with seat selection',
      ...
    ]
    system      = 'You are Voyager, a travel concierge for Gjallar...'

  ✓ wrote gjallar_deploy.py
  next:
    1. Open gjallar_deploy.py and replace any remaining TODOs.
    2. python gjallar_deploy.py
    3. gjallar verify http://localhost:8080

The file is written directly. No prompts, no confirmation. If you'd rather preview-and-edit before writing:

Flag What it does
(none — default) Generate the proposal, print it, write the file. Done.
--review Show the proposal and prompt [Y/n/e/?] before writing. Useful when you want a final sanity check.
--dry-run Show the proposal but DON'T write the file. Useful for previewing what would be generated.

Under --review:

Key What it does
Y (default) Write gjallar_deploy.py with the proposal as-is.
n Decline. No file written. Exit 0.
e Open $EDITOR on a TOML tempfile of the 5 fields. Edit, save, close — the CLI re-renders the file from your edits.
? Print the model's reasoning, then re-prompt.

How much is automated

Before this release, gjallar wrap left 4 placeholders for you to edit by hand. After this release, the LLM fills all 5:

Field Auto-filled by LLM?
name ✓ from project README / pyproject
description ✓ from README + source
industry ✓ inferred from project domain
capabilities ✓ from README + tool function signatures + source
system (personality) ✓ from existing agent instruction / safety patterns
Agent import line (e.g. from agent import root_agent as my_agent) ✓ when the LLM can identify a single confident entry point

For multi-agent / ambiguous projects, the import line falls back to a # TODO (1 of 1): point this at your existing agent placeholder for you to fill in manually. For single-agent projects (the common case), there is no manual editing required — accept with Y and the file is deployable as-is (modulo your usual deploy steps).

What gets sent to the LLM provider

Only files the analyzer chooses to include — typically README, the project manifest (pyproject.toml / package.json), and files that import the agent framework. Everything else is filtered out before the API call:

  • .env, .env.*, .secrets, *.pem, *.key — never read.
  • .git/, .venv/, node_modules/, dist/, __pycache__/ etc. — pruned.
  • Files containing API-key-shaped strings (sk-..., ghp_..., AKIA..., inline password = "...", inline api_key = "...") — skipped with a reason on the --dry-run report (coming in a future release).
  • A previously-generated gjallar_deploy.py — excluded, so the LLM doesn't loop on its own prior output.

Total context is capped at ~30,000 tokens. Larger projects get a priority-ordered selection (README first, then manifest, then framework-importing files, then entry candidates, then mtime-newest).

When to NOT use it

  • If you pass any of --name, --description, --capabilities, --industry, --system, or --no-llm: the analyzer is skipped.
  • If no provider API key is set: you get today's manual prompts plus a one-line tip showing the three supported env-var names.
  • If your project's source files contain LLM API keys: those files are filtered out, and if everything important is filtered, you'll see a banner that the analyzer fell back to manual.

Costs

Per wrap invocation: roughly 30K input tokens + 500 output tokens. You pay your LLM provider directly; Gjallar bills nothing for this feature in any of the three BYOK paths.

Provider Typical cost per wrap
Anthropic (Haiku 4.5) ~$0.03
OpenAI (gpt-4o-mini) ~$0.005
Gemini (2.0-flash) ~$0.003

Coming in later sessions

A hosted endpoint (so users without any LLM key can use the feature), per-account quotas, an gjallar login flow, server-side canonicalization of capabilities + industry, and a quality eval harness all ship in subsequent releases.

Telemetry (opt-in)

gjallar wrap collects anonymous usage data to help us understand where users get stuck. On first run, you'll be asked Y/n. Default is yes.

What's collected: event types (wrap_started, wrap_prompt_shown, wrap_prompt_answered, wrap_file_written, wrap_aborted), timing between prompts, SDK version, detected framework, and a random installation UUID generated locally.

What's NOT collected: any file content, file paths, project contents, or anything tying the installation_id to a Gjallar account.

Opt out:

export AGENTHUB_TELEMETRY=off
# or
rm ~/.config/gjallar/config.json

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gjallar-0.13.0.tar.gz (420.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gjallar-0.13.0-py3-none-any.whl (103.2 kB view details)

Uploaded Python 3

File details

Details for the file gjallar-0.13.0.tar.gz.

File metadata

  • Download URL: gjallar-0.13.0.tar.gz
  • Upload date:
  • Size: 420.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for gjallar-0.13.0.tar.gz
Algorithm Hash digest
SHA256 9a883e5c32c082de440a6401e14de35ed190e2a28c7210d8caaab7500eee447e
MD5 5da50e6679fb83001dd7386c33b32762
BLAKE2b-256 298915a2f08fc7b15e09bccb02ed8cf39a8aa380d5a0db613576c1f9d662e040

See more details on using hashes here.

File details

Details for the file gjallar-0.13.0-py3-none-any.whl.

File metadata

  • Download URL: gjallar-0.13.0-py3-none-any.whl
  • Upload date:
  • Size: 103.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for gjallar-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c2b13f671fb78c576a6f73c3c328d0b9e9cce0c61655a991285d52f3e4682f32
MD5 ec735887690665167ecda9b34fd1bcef
BLAKE2b-256 ff1d41570f829c13efd8a547ae95d780ee1cf63c80e6dc24acbeef86c62f66a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page