Skip to content

feat(api): LoopRegistry + REST /api/loops (daemon-native loop lifecycle)#1260

Open
kienbui-eup wants to merge 3 commits into
RightNow-AI:mainfrom
kienbui-eup:feat/api-loops
Open

feat(api): LoopRegistry + REST /api/loops (daemon-native loop lifecycle)#1260
kienbui-eup wants to merge 3 commits into
RightNow-AI:mainfrom
kienbui-eup:feat/api-loops

Conversation

@kienbui-eup

Copy link
Copy Markdown

Daemon-native, project-aware loop registry so scheduled/event-driven agent loops have a first-class lifecycle and can be inspected by any client (not just the editor).

What

  • openfang-api/src/loops.rs: LoopRecord + LoopRegistry persisted atomically to ~/.openfang/loops.json (tmp + rename), loaded at boot. The daemon stores + orchestrates loops but treats the client-rendered manifest_toml + turn_prompt as opaque — the pattern/prompt library stays client-side.
  • REST /api/loops (reuses existing kernel primitives):
    • POST /api/loopsspawn_agent + cron_create (every/cron) or register_trigger (event), persist record
    • GET /api/loops — list records
    • DELETE /api/loops/{id} — remove binding + kill_agent (workspace state files kept)
    • POST /api/loops/{id}/run — send the turn prompt to the agent
    • PUT /api/loops/{id}/level — kill + respawn + rebind at a new rollout level
    • GET /api/loops/{id}/runs — read the workspace loop-run-log.md
  • Cron job names sanitised (CronJob::validate rejects :); loop ids are server-generated uuids (no path traversal in the run-log read).

Verification

cargo check -p openfang-api and --tests EXIT 0. The loop→cron job JSON was checked field-by-field against CronSchedule/CronAction/CronDelivery. Editor thin-client that consumes this API: kienbui-eup/AntiDev#2.

🤖 Generated with Claude Code

kienbui-eup and others added 3 commits June 3, 2026 16:55
Daemon-native, project-aware loop registry so loops have a first-class
lifecycle and can be inspected by any client, not just the editor.

- loops.rs: LoopRecord (id, pattern_id, workspace, level, trigger, safety,
  manifest_toml, turn_prompt, agent_id, binding_id, enabled) + LoopRegistry
  persisted atomically to ~/.openfang/loops.json (tmp + rename), loaded at boot.
  The daemon stores and orchestrates loops but treats the client-rendered
  manifest and turn prompt as opaque - the pattern library stays editor-side.
- AppState carries the registry; endpoints reuse the existing kernel:
  - POST   /api/loops        spawn_agent + cron_create (every/cron) or
                             register_trigger (event), then persist the record
  - GET    /api/loops        list records
  - DELETE /api/loops/{id}   remove binding + kill_agent (state files kept)
  - POST   /api/loops/{id}/run       send the turn prompt to the agent
  - PUT    /api/loops/{id}/level     kill + respawn + rebind at a new level
  - GET    /api/loops/{id}/runs      read the workspace loop-run-log.md
- Cron job names are sanitised (CronJob::validate rejects ':'), loop ids are
  server-generated uuids (no path traversal in the run-log read).

Verified: cargo check -p openfang-api and --tests EXIT 0; the job JSON matches
CronSchedule/CronAction/CronDelivery field-by-field against the kernel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round out the /api/loops lifecycle so a client can pause/resume and edit a loop
without deleting and recreating it.

- POST /api/loops/{id}/enable {enabled}: flips the schedule binding
  (cron_scheduler.set_enabled for every/cron, set_trigger_enabled for event) and
  persists record.enabled.
- PUT /api/loops/{id}: replaces the loop's config - remove old binding, kill old
  agent, spawn from the new manifest, rebind from the new trigger/param, and
  update trigger/safety/level/prompt while keeping id/pattern_id/workspace/
  created_at. Cron names go through sanitize_cron_name. On a mid-update failure
  the record is left pointing at no dead agent (agent_id/binding_id cleared).

Reuses set_loop_level, toggle_cron_job and the create_loop binding block.

Verified: cargo check -p openfang-api and --tests EXIT 0; route table has a single
/api/loops/{id} entry (.delete().put()), no double registration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant