Skip to main content

Autonomous, DSL-fluent coding agent built on the Claude Agent SDK — point it at any DSL directory and it plans, codes, debugs, verifies, and self-extends.

Project description

sceneprogagent

An autonomous, DSL-fluent coding agent built on the Claude Agent SDK.

Point it at a DSL directory — a folder of .py/.md/.html files describing your domain API. The agent plans, retrieves the right DSL symbols, writes code, runs it the way your DSL says to, debugs until the result is verified correct, and can autonomously extend the DSL when a task needs a capability it lacks.

It is domain-agnostic: 3D generation, Blender, SfM, scientific simulation, or any other domain is just a different DSL directory. No domain assumptions live in this package.


Prerequisites

Requirement Notes
Python ≥ 3.10
Claude Code CLI npm install -g @anthropic-ai/claude-code (or download from claude.ai/code)
Authentication — see below
git Only needed for autonomous DSL self-extension (extension_enabled=True)

Authentication

The agent drives the claude CLI as a subprocess — auth is handled by that process. Pick whichever method fits your setup:

Option A — Anthropic API key (pay-per-token, from console.anthropic.com)

# shell (recommended for scripts / CI)
export ANTHROPIC_API_KEY=sk-ant-...

Or pass it programmatically — no shell env var needed:

agent = SceneProgAgent(AgentConfig(dsl_dir="./dsl", api_key="sk-ant-..."))

Or via the CLI flag:

sceneprogagent run --dsl ./dsl --api-key sk-ant-... "My task"

Option B — Claude.ai / Claude Code login (Claude Pro/Max/Teams subscription)

claude auth login    # one-time; stores credentials in ~/.claude/.credentials.json

After that, no API key is needed — the subprocess reads the stored credentials automatically.

Running as root (Docker)

AgentConfig automatically sets IS_SANDBOX=1 for the CLI subprocess when permission_mode="bypassPermissions" (the default). You do not need to set it manually.

If the CLI is not on PATH, tell the agent where to find it:

export CLAUDE_CODE_EXECPATH=/path/to/claude   # or pass cli_path= to AgentConfig

Install

From PyPI (recommended)

pip install sceneprogagent

With optional embedding-based doc search:

pip install "sceneprogagent[embed]"

From source

git clone https://github.com/KunalMGupta/sceneprogagent
cd sceneprogagent
pip install -e .              # core
pip install -e ".[embed]"     # + chromadb / sentence-transformers
pip install -e ".[dev]"       # + pytest

Quick start (5 minutes)

1 — Point at your DSL directory

Your DSL directory needs at minimum one .py file with the API and a runbook — a markdown file telling the agent how to execute scripts with this DSL.

my_project/
├── dsl/
│   ├── api.py          ← your domain Python library
│   ├── RUNBOOK.md      ← execution recipe (see below)
│   └── docs.md         ← optional extra docs
└── solve.py            ← your script using sceneprogagent

dsl/RUNBOOK.md (minimal):

# My DSL

Import the API with `import api` and call its functions.

## How to run code
```run
python3 {file}
```

The ```run block is the execution recipe. {file} is replaced with the path to each generated script. For a Blender DSL it might be blender --background --python {file}; for SfM it might be python3 {file} with a special env.

2 — Run from the CLI

# one-shot task
sceneprogagent run --dsl ./dsl "Create a red cube and save it to output.blend"

# save the final verified program
sceneprogagent run --dsl ./dsl --save-code final.py "Create a red cube"

# iterative optimization
sceneprogagent run --dsl ./dsl --optimize "minimise polygon count" "Create a sphere"

# disable autonomous DSL self-extension
sceneprogagent run --dsl ./dsl --no-extend "Create a box"

3 — Use from Python

from sceneprogagent import AgentConfig, SceneProgAgent

with SceneProgAgent(AgentConfig(dsl_dir="./dsl")) as agent:
    summary = agent.run("Create a scene with a box named 'table' of size 2 and save it")
    print(agent.final_code)   # the final verified program

Deploying in a new project

Step 1 — Install

pip install sceneprogagent

Step 2 — Create a DSL directory

A DSL directory needs:

  • At least one .py file with your domain API (public functions, well-named, with docstrings).
  • A runbook named RUNBOOK.md (also discovered as AGENT.md, DSL.md, or README.md).

The runbook teaches the agent two things: (1) how to execute scripts, via the ```run block; (2) any domain conventions and gotchas, in plain markdown prose.

Example structure:

# My Domain DSL

Use `import myapi` to access all functions.

All functions work with absolute paths. Pass `save=True` to persist results.

## How to run code
```run
python3 {file}
```

## Tips
- Always call `myapi.init()` first.
- `myapi.save(path)` requires the parent directory to already exist.

Step 3 — Configure and run

from sceneprogagent import AgentConfig, SceneProgAgent

config = AgentConfig(
    dsl_dir="./dsl",             # path to your DSL directory
    work_dir="./agent_runs",     # scratch dir for code attempts + logs (auto-created)
    model="claude-sonnet-4-6",   # any Claude model id (Opus for the hardest tasks)
    max_turns=80,                # agentic tool round-trips per run
    max_debug_iters=8,           # debug retries before giving up
    max_budget_usd=1.00,         # optional spend ceiling
    extension_enabled=True,      # let the agent extend the DSL when needed
    save_code="out/final.py",    # write the final program here
)

with SceneProgAgent(config) as agent:
    summary = agent.run("Your task description here")
    print(summary)
    print(agent.final_code)

Python API reference

AgentConfig

Parameter Default Description
dsl_dir (required) Path to the DSL directory
work_dir <dsl>/../.sceneprog_work Scratch dir for code attempts, logs
runbook_path auto-discovered Override runbook location
api_key None Anthropic API key (sk-ant-...). Falls back to ANTHROPIC_API_KEY env var or stored claude auth login credentials
model "claude-sonnet-4-6" Claude model ID (use "claude-opus-4-8" for hard tasks)
max_turns 80 Max tool round-trips per run()
max_debug_iters 8 Max debug retries before giving up
max_budget_usd None Optional spend ceiling per run
extension_enabled True Allow autonomous DSL self-extension
embed_enabled False Use chromadb embeddings for doc search
save_code None Default path to write the final program
cli_path auto-discovered Override path to the claude CLI binary
permission_mode "bypassPermissions" Claude SDK permission mode
extra_env {} Extra env vars for the subprocess

SceneProgAgent

agent = SceneProgAgent(config)

# Solve one task (synchronous)
summary: str = agent.run(task, save_code=None)

# Iterative optimization
summary: str = agent.optimize(task, objective, max_rounds=3, save_code=None)

# Async variant (use inside an existing event loop, e.g. FastAPI, Jupyter)
summary: str = await agent.run_async(task, save_code=None)

# Result of the last run
agent.final_code    # str | None — the final verified program
agent.retriever     # query the DSL index without a model call

# Lifecycle
agent.close()       # close the trajectory log when done
# or use as a context manager:
with SceneProgAgent(config) as agent:
    ...

Init once, run many

A single SceneProgAgent is reusable — initialize once and solve tasks sequentially. Each call to run() resets per-task result tracking; the trajectory log spans the whole session.

with SceneProgAgent(AgentConfig(dsl_dir="./dsl")) as agent:
    for i, task in enumerate(my_tasks):
        summary = agent.run(task, save_code=f"out/task_{i}.py")
        results[i] = agent.final_code

How it works

task ─▶ plan ─▶ retrieve DSL context ─▶ write code ─▶ run via runbook ─▶ verify
                      ▲                                     │              │
                      └──────────── debug loop ◀────────────┘   fail ◀─────┘
                                                                  │ (capability missing)
                                                         autonomous DSL extension
  • Hybrid retrieval grounds every call in the real API — no hallucinated functions:
    • dsl_overview — compact cheatsheet of all public symbols (always in context);
    • dsl_lookup — real signatures, docstrings, and source for any symbol;
    • dsl_search_docs + built-in Grep/Read for depth (optional embeddings via [embed]).
  • Verification oracle (verify_solution): clean exit + an assertion script the agent writes, so "success" means the answer is correct, not just non-crashing.
  • Autonomous self-extension: git snapshot → WebSearch/clone → translator subagent adapts external code into DSL-style files → reindex → smoke-test → use. restore_dsl rolls back a bad extension.
  • Trajectory log — every tool call, code submission, and verification result is written to <work_dir>/trajectory.jsonl; every generated script is kept in <work_dir>/code/attempt_NNN.py.

What the agent can and can't guarantee

Will reliably do:

  • Read and use your DSL's actual API (not hallucinate functions)
  • Run the generated code and show you the output
  • Retry with the real error/traceback until it passes its own assertions
  • Report final_code only when a program ran cleanly and passed verification

May still fail when:

  • The task requires DSL capabilities that don't exist (use extension_enabled=True to let the agent add them)
  • The agent misunderstands the task and asserts the wrong thing — phrasing matters; "verify it equals 17" beats "check it's roughly right"
  • max_debug_iters is reached without a solution — increase it or break the task into smaller pieces

Advanced: querying the DSL index directly

agent = SceneProgAgent(config)

# No model call — instant, free
print(agent.retriever.cheatsheet())         # all public symbols
print(agent.retriever.lookup("my_func"))    # signature + source
print(agent.retriever.search_docs("save"))  # heading / prose search

This is useful for building your own tooling on top of the DSL index — e.g. autocomplete, a REPL helper, or a CI check that the DSL API hasn't changed.


Cost & performance

Most per-run cost comes from how much context is sent on every agentic turn. Three levers, biggest first:

1. Keep the DSL directory scoped to DSL source. The agent builds an API cheatsheet from every .py file under dsl_dir and keeps it in context on every turn. If the directory also contains a vendored runtime (a bundled interpreter, a site-packages, a whole app distribution), the cheatsheet fills with thousands of junk symbols — you pay for them every turn and the agent can't see the real API, so it burns extra turns rediscovering it. This is usually the dominant cost.

  • Vendored interpreter trees (lib/python3.x, site-packages, dist-packages) are auto-skipped.

  • For anything else, add a .sceneprogignore (gitignore-style) at the DSL root:

    # dsl/.sceneprogignore
    blender-4.5.4-linux-x64/   # bundled app/runtime — not the DSL API
    __pycache__/
    *.generated.py
    

    Check the effect with no model call:

    agent = SceneProgAgent(config)
    print(agent.retriever.cheatsheet())   # should show only your real DSL files
    

2. Model choice. The default is claude-sonnet-4-6 — strong at DSL codegen and ~5× cheaper than Opus. Use model="claude-opus-4-8" only for the hardest tasks.

3. Keep the runbook lean. The full runbook text is injected on every turn (it rides prompt-caching, but still counts). A very large worked example can be moved into a separate doc the agent pulls on demand via dsl_search_docs/Read instead of living in the always-injected runbook.


Module layout

Module Responsibility
config.py AgentConfig: paths, model/budget, toggles, CLI + sandbox env
runbook.py discover/parse the runbook and its execution recipe
index/ static (AST) API index + cheatsheet; optional embedding store
retrieval.py hybrid retriever (overview / lookup / docs)
execution.py run generated code via the runbook recipe
verify.py layered verification oracle (exit code + assertions + optional LLM judge)
extension.py git snapshot/restore + repo cloning primitives
tools.py the sceneprog MCP tool server (all agent-callable tools)
prompts.py system prompt + debugger/translator subagent definitions
agent.py main orchestrator — assembles options, drives the SDK loop
optimize.py iterative-optimization loop
telemetry.py JSONL trajectory logging
cli.py command-line entry point

Tests

pytest                              # offline: index, retrieval, runbook, execution, verify
SCENEPROG_RUN_E2E=1 pytest -s       # + live agent tests (use real Claude; cost money/time)

Building and publishing to PyPI

pip install build twine

# build the distribution
python -m build                     # produces dist/sceneprogagent-*.whl and .tar.gz

# upload to TestPyPI first
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ sceneprogagent

# when satisfied, publish to PyPI
twine upload dist/*

To bump the version, edit version = "..." in pyproject.toml and __version__ in sceneprogagent/__init__.py, then rebuild.


License

MIT © Kunal Gupta

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

sceneprogagent-0.1.5.tar.gz (43.7 kB view details)

Uploaded Source

Built Distribution

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

sceneprogagent-0.1.5-py3-none-any.whl (42.2 kB view details)

Uploaded Python 3

File details

Details for the file sceneprogagent-0.1.5.tar.gz.

File metadata

  • Download URL: sceneprogagent-0.1.5.tar.gz
  • Upload date:
  • Size: 43.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for sceneprogagent-0.1.5.tar.gz
Algorithm Hash digest
SHA256 e1ebc54bf8c839553c89f99973b905593ef2f4e55c291ff1dabb98af166424bc
MD5 07ac01d7e0982aae559e6bb1ed8a702c
BLAKE2b-256 44f4d4a38e3566c969add29be81b92994a6f50d29721a4514419e03f4c55dc37

See more details on using hashes here.

File details

Details for the file sceneprogagent-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: sceneprogagent-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 42.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for sceneprogagent-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 a1cea1a84b76040267d794d522ff24b08f8f72d7213badea7c14c4aaed0b1847
MD5 4df7dd55f590ef72454af9c4732a0f4e
BLAKE2b-256 7290434b94c22b719328c3330b8c6a45db2ebcd30cfcacf553dba24f12b77095

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