Skip to content

Add JSON output, a Python API and a GitHub Action - #81

Merged
nasser1941 merged 1 commit into
mainfrom
feat/automation
Oct 5, 2026
Merged

nasser1941 merged 1 commit into
mainfrom
feat/automation

Conversation

@nasser1941

Copy link
Copy Markdown
Owner

Closes #48.

What

  • Structured output.
    • lcode -p "…" --output json prints one result object: status, text, tool_calls (arguments, output, error), files_changed (from the request's checkpoint, so shell commands count too), usage, seconds, session_id.
    • --output stream-json prints a start event, then an assistant event per model step and a tool_result per call, then the same result.
    • --max-steps N and --allowed-tools LIST limit a run.
    • Exit codes: 0 success, 1 error, 3 stopped at max steps, 130 interrupted.
    • lcode's usual output goes to stderr. With nobody to ask, permission requests are refused with a message telling the model so; refused calls are marked as errors.
  • Python API: from lcode import Session (loaded lazily, so import lcode stays fast).
    • run() returns the Result; stream() yields the events.
    • approve(request) decides permission requests; for shell commands, the target is the command without its cd <dir> && prefix.
    • cmd_chat now sets up its agent through the same api.open_agent(), which raises SetupError instead of exiting.
    • Example: examples/api/fix_tests.py.
  • GitHub Action: action.yml (composite) runs lcode action on a self-hosted runner.
    • Triggers: @lcode comments from owners, members and collaborators (bots are ignored), and, with review: true, new and updated pull requests (forks only with review-forks).
    • Changes on an issue go to a new branch, committed as lcode, with a pull request that closes the issue. On a pull request, they're pushed to its branch.
    • Git is lcode's job: the model's instructions say so, and deny rules block git commit/push/checkout/switch/… and gh. Its reply is asked for after the change is proposed, so it describes the change instead of talking about committing.
    • Commands: allow-commands lets the model run tests in auto-edit mode. The prompt marks issue text and code as information, not commands.
    • lcode version: the action runs the version matching its tag, through uvx, an installed lcode, or pip in a virtual environment.
  • Docs: a new Automation page covering the JSON schema and events, the Python API, the action's inputs and an example workflow, setting up the runner, and safety.

Tests

  • tests/test_api.py (7 tests): results, files and usage; approve; max steps; errors; unknown tools; streaming; multi-turn sessions; --output json and stream-json through cli.main; JSON errors before a run starts.
  • tests/test_action.py (6 tests), with a fake GitHub API, a bare remote and a scripted model:
    • who can trigger it;
    • an issue request becoming a pull request (branch, commit author, body, comment, reaction, token);
    • git steps denied to the model;
    • a question answered without changes;
    • failures reported in a comment;
    • pushing to a pull request's branch;
    • reviews changing nothing.
  • Real runs with Qwen3.6 35B through Ollama:
    • --output json: fixed two bugs and reported calc.py as changed, with 6 requests and about 35K prompt tokens.
    • --output stream-json --max-steps 3: the expected events, exit code 3, and the refused bash call marked as an error.
    • examples/api/fix_tests.py: approved only pytest; fixed the code; tests pass.
    • lcode action on a simulated @lcode issue comment, with a fake GitHub API and a bare remote. lcode reacted, ran the tests through allow-commands, and pushed lcode/issue-4-… with the fix. It opened a pull request that closes /undo and automatic checkpoints before changes #4 and replied with a clean summary.
      • Earlier versions let the model try to commit, then say so in the reply. That's why git is now denied to the model, the instructions say lcode handles it, and the reply is written after the change is proposed.
  • Not tested on a real GitHub self-hosted runner. That needs a runner registered to a repository; the docs explain the setup.

🤖 Generated with Claude Code

lcode -p --output json prints one result object (status, answer,
tool calls, files changed, usage); --output stream-json prints an event
per model step and tool result, then the result. --max-steps and
--allowed-tools limit a run, and the exit code says how it ended.
Without a person to ask, permission requests are refused.

from lcode import Session runs requests from Python: run() returns the
same result, stream() yields the events, and approve() decides
permission requests. cmd_chat now sets up its agent through the same
open_agent().

lcode action, with action.yml, runs lcode as a GitHub Action on a
self-hosted runner: it answers @lcode in issues and pull requests from
trusted people, commits changes to a new pull request (or the pull
request's branch), and can review pull requests. The model can't run
git steps itself, and its reply is written after the change is
proposed.

Closes #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nasser1941
nasser1941 merged commit 97bf339 into main Oct 5, 2026
12 checks passed
@nasser1941
nasser1941 deleted the feat/automation branch October 5, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automation: JSON output, a Python API and a GitHub Action for self-hosted runners /undo and automatic checkpoints before changes

1 participant