Skip to content

Documentation navigation

Local file triggers

Observe local file metadata with explicit watching and durable delivery retries.

#Start an agent when a local file arrives or changes

A local watcher observes a folder you choose and sends signed metadata to your platform. It sends the relative filename, size and modification time, never the file's contents or your absolute folder path. No public listener, tunnel or paid relay is needed. The watcher runs only when you explicitly start it.

#Save a connection

In Studio, open Config → Triggers → Connections → Add a connection. In the Developer Portal, open your project's Integrations → Connections. Choose Local files, give it a name, and save its ID, callback and one-time signing secret. Creating this connection does not start watching your machine.

From a terminal, use a private password prompt:

Shell
maivn connections create --project-id YOUR_PROJECT_ID --email YOU@example.com --name "Invoices folder" --kind local_file_watch --secret-file ~/.maivn-private/invoices.json

The new owner-private JSON file contains the connection receipt and its one-time secret. See terminal setup for private-directory requirements. Use that saved secret at the watcher's private prompt or supply it through your secret store using --secret-env.

Alternatively, connection creation uses the existing signed-in connection API with this body:

JSON
{"display_name":"Invoices folder","setup_kind":"local_file_watch"}

Keep this signing secret separate from your agent's project API key. Save it in your private secret store; do not put it in your agent source or shell arguments.

#Declare and serve the agent

The source checkout includes a runnable app at apps/maivn-demos/demos/features/triggers/local_file_arrival.py. Replace its FILE_CONNECTION_ID with your saved connection ID. In Studio, Scan repo, add Local File Demo, choose the project and follow the Invoice arrives checklist to Run it from Studio and Turn on. For terminal use from apps/maivn-demos, run maivn serve demos.features.triggers.local_file_arrival:file_agent --project-id PROJECT --agent-id file-arrival --version 1. After Announced, register from another terminal with python -m demos.features.triggers.local_file_arrival --register.

Choose a Watch ID such as invoices. Use the same ID in the declaration and the watcher. Each Watch ID selects its own events, even if they share a connection.

Python
arrival = (
    agent.on_file_change("invoices", connection_id="conn_your_files")
    .key("invoice-arrival")
    .named("Invoice arrives")
    .target("invoice-agent", version=1)
    .asking(
        "Describe this file metadata: {{event.payload.relative_path}}, "
        "{{event.payload.size_bytes}} bytes. File contents were not supplied."
    )
    .display_in_app()
)

The default operation is created. Use operation="modified" for changes to an already observed file. Deletions do not emit events. Calling .where() adds a filter without removing the Watch ID selector. Importing this declaration does not start a watcher, register the trigger or run a model.

Turn on the trigger and run its agent in Studio. For terminal use, configure the project API key and plane origins, then follow Serve your agent. After the serving process reports Announced, call arrival.invoke() from a separate explicit registration script. Keep the serving process open.

#Start watching in Studio

Open Config → Triggers → Local file watchers. Choose your saved Local files connection, enter the same Watch ID as your declaration, and choose the local folder and glob. Paste the saved secret into the password field and click Start watcher. The input clears after the request. Nothing starts merely because you open the panel or reload the app.

The status reports accepted and pending event counts. Accepted means the receiver saved the event; use the trigger's run history to inspect the answer. Stop watcher stops observation and preserves pending deliveries. Closing Studio also stops watching. Start again explicitly with the same configuration and secret to recover its journal. Changing the app's project first stops its old watchers so an invisible process cannot keep sending to the old project.

The Completed writes and existing files disclosure contains recursive, completion-marker and initial-file options. Studio observes the filesystem of the machine running Studio, which may differ from the computer displaying its page. Absolute folder paths stay in local Studio configuration and status; platform events contain only relative paths.

#Start watching from your terminal

Create an incoming folder. Then run:

Shell
maivn watch-files ./incoming --watch-id invoices --connection-id conn_your_files --glob "*.pdf"

Paste the signing secret at the private prompt. The default receiver is this connection's events path on MAIVN_BASE_URL, or on the local platform at http://127.0.0.1:8000 when that is unset. For another platform, add --receiver-url "https://YOUR_PLATFORM/v1/connections/conn_your_files/events" using the exact callback for this connection. The endpoint must be HTTPS unless it is loopback HTTP. The command does not create a tunnel.

Keep the terminal open. It reports watching, pending or queue_full with counts and the last HTTP result. accepted_events means the receiver saved the event; open the resulting trigger run to verify the agent completed. Ctrl-C stops the watcher and preserves pending deliveries. Portal cannot start a local process; it provides the command to run on your device.

For automation, provide one secret line on stdin or use --secret-env NAME for an already configured private environment variable. Never supply the secret as a command-line value. The producer uses Python's standard library and the existing mAIvn SDK; it requires no new watcher service or subscription.

#Finish writing before publishing

Existing files form a baseline on the first scan. Create a new file after the watcher starts, or explicitly use --emit-existing with new watcher state. The default polling interval is 0.5 seconds and the quiet period is 1 second. Writes inside that quiet window are coalesced. A quiet window cannot prove that an arbitrary writer has finished.

Prefer writing invoice.pdf.tmp, closing it, then renaming it to invoice.pdf. Common temporary suffixes are excluded. For producer-confirmed completion:

Shell
maivn watch-files ./incoming --watch-id invoices --connection-id conn_your_files --glob "*.pdf" --completion-suffix .ready

Finish the file, then create invoice.pdf.ready. For every subsequent file revision, replace the marker after finishing that revision. The marker must be at least as new as its file, and its identity must change. Marker contents are not read. The quiet period still coalesces metadata changes after completion.

Only regular files are observed. The default is nonrecursive; use --recursive to include subfolders. Symlinks, junctions and paths escaping the selected root are excluded. Globs use forward slashes and case-sensitive matching against relative paths. A temporary access failure pauses scanning rather than turning an unchanged file into a new arrival.

#Restart and recover

An owner-private SQLite journal outside the watched folder records each event's UUID, immutable metadata and file cursor in one transaction. If the receiver is offline or the acknowledgement is lost, the watcher retains and retries the same event. Restart with the same configuration to resume. The receiver deduplicates that UUID; a retry does not create another run.

Default state lives under ~/.maivn/watch-files/, separated by configuration. For explicit storage, pass --state-file PATH in a new private directory outside the watched folder. Shared existing state directories are refused. One process may use a state file at a time; its operating-system lock releases on exit or process death. Keep pending state when troubleshooting.

At 10000 pending events, the watcher reports queue_full and stops advancing unrecorded changes until deliveries drain. It observes final changes after downtime; it cannot reconstruct intermediate writes that occurred while no observer ran. This is metadata notification, not file-content ingestion or a filesystem audit log. Uploading files requires a separate workflow you choose.

Each scan also has a default and maximum limit of 100000 directory entries, including unmatched files and directories. Use --max-entries to lower it. Exceeding the bound reports scan_limit and stops without advancing the cursor. Choose a smaller folder or reduce recursive coverage, then restart. A glob filters emitted events; it does not reduce how many directory entries must be examined.

Pause the trigger to stop agent runs. Stop the local watcher to stop observing. Revoke the connection to refuse further signed deliveries; its pending local events remain available for diagnosis. These are separate actions.