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
.pyfile with your domain API (public functions, well-named, with docstrings). - A runbook named
RUNBOOK.md(also discovered asAGENT.md,DSL.md, orREADME.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-opus-4-8", # any Claude model id
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-opus-4-8" |
Claude model ID |
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 →
translatorsubagent adapts external code into DSL-style files → reindex → smoke-test → use.restore_dslrolls 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_codeonly when a program ran cleanly and passed verification
May still fail when:
- The task requires DSL capabilities that don't exist (use
extension_enabled=Trueto 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_itersis 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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sceneprogagent-0.1.2.tar.gz.
File metadata
- Download URL: sceneprogagent-0.1.2.tar.gz
- Upload date:
- Size: 39.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
099fe3f490be02a261abcea6739cdeac47394a4502ce23d22ece03a8fe562bc9
|
|
| MD5 |
4e23e2681a1a1b89a9888c0b68df18ce
|
|
| BLAKE2b-256 |
b6277e0020b1577051672d843faef69264cebe3d3f120b41254b7ab3466e4f07
|
File details
Details for the file sceneprogagent-0.1.2-py3-none-any.whl.
File metadata
- Download URL: sceneprogagent-0.1.2-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bcbe4880b0e87d55e9b9049d5893a0e587ae462f4e8360726880d3de7099c3f
|
|
| MD5 |
102fd72ebfe9fa4ac0ec8e7cc39179f9
|
|
| BLAKE2b-256 |
fea01190c89f1549cc4c284b679ffb2f9cb1d61f782bd0c599eaa2165669fd6b
|