Skip to content
ochat
Search documentation

Use quotes for an exact phrase.

Search by topic, command, or code identifier.

    GitHub ↗

    Run a background agent

    Build a daemon-hosted workflow with timers and background events.

    View Markdown source ↗

    Schedule a bounded timer and observe the host stop the session after the client disconnects.

    Complete the Unix daemon tutorial and use a separate private demo directory. Run the checkout commands from the repository root with its active opam environment. This first example uses a detached daemon host and requires no provider key when you send no model messages. ChatML and scheduling are host-dependent; native local exit would stop its process.

    Read the current provider TLS and permission boundaries before model work or deployment. Build troubleshooting includes the Apple Silicon/OpenBLAS setup path.

    This first example uses a timer, not a paid model request. Prepare the private tutorial directory. Edit the private unix.sexp prompt entry’s path to the absolute path of the tracked timer.chatmd. Keep its configured ID hello and workspace project; only the source path changes.

    Validate and start the daemon as in the Unix tutorial. In another terminal create a detached session:

    Terminal window
    dune exec bin/chat_tui.exe -- --no-config --connect "unix://$OCHAT_DEMO/agent.sock" \
    --new-daemon-session --prompt hello --workspace project --detached

    The moderator schedules Tick after ten seconds at session start. Quit the TUI before ten seconds elapse. The daemon, not the client, delivers the event; the handler requests session end with scheduled tutorial stop. List/reconnect after the timer fires and inspect session/schedule state. Do not submit a user message: that would add an unrelated model turn to an intentionally offline example.

    With the stdio client, schedule.list takes the returned session_id and positive limit; session.get shows session state. Initialize a new connection before reattachment. If using a TUI to inspect a stopped session, attachment is not a request to restart its script.

    The orchestration guide describes Model.spawn, completion events, budgets, jobs and cancellation. Use the complete tested background recipe example as an embedding/script reference: agent_prompt_v1 takes a JSON object containing the prompt, input and is_local, and completion handlers dispatch by job ID. Replace fake-provider setup only after choosing real credentials/model/budget.

    For an unattended production script, configure narrow tools, explicit permission fallback, job limits and misfire policy. Keep detached liveness if no client must own it. Check job.list/get and schedule.list/get after reconnect; cancel with a writable authorized attachment. Stop the daemon normally when done. On restart, durable scheduling intent and delivery records recover, but in-flight external effects are not automatically safe to rerun.

    Checkpoint, troubleshooting, and next step

    Section titled “Checkpoint, troubleshooting, and next step”

    After the timer fires, session.get should report the ended session and schedule.list should show the delivered timer. A disconnected client alone is not evidence of background execution. If nothing happens, check that the private prompt path names timer.chatmd, the daemon stayed alive, and the session was created after the configuration change. The example schedules at session start; reconnecting is not a request to restart the script. Shut down clients and daemon before archiving/removing the recorded private directory; durable state persists there until then. Next, inspect scheduling through the stdio client or study orchestration.

    View source · Background timer2 files

    Complete source files. Open a filename to read it here.

    timer.chatmdStart here
    <developer>This host demonstrates a background timer; no model request is needed.</developer>
    <script language="chatml" kind="moderator">
      type state = int
      type event = [ `Session_start | `Tick ]
      let initial_state = 0
      let on_event : context -> state -> event -> state task =
        fun ctx st ev ->
          match ev with
          | `Session_start ->
            Task.bind(Schedule.after_ms(10000, `Tick), fun timer_id ->
            Task.pure(st + 1))
          | `Tick ->
            Task.bind(Runtime.end_session("scheduled tutorial stop"), fun ignored_end ->
            Task.pure(st + 1))
    </script>
    

    Link to this fileDownload this file

    LICENSE.txtnotice
    MIT License
    
    Copyright (c) 2025 
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

    Link to this fileDownload this file