feat: wire Gerhard to gutasktool
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# Gerhard scheduled jobs
|
||||
|
||||
Hermes stores live scheduled jobs in:
|
||||
|
||||
- `agents/gerhard-hermes/cron/jobs.json`
|
||||
|
||||
That file is read by `hermes cron list`, `hermes cron create`, and the gateway scheduler.
|
||||
It also contains mutable runtime state, such as:
|
||||
|
||||
- `next_run_at`
|
||||
- `last_run_at`
|
||||
- `last_status`
|
||||
- repeat counters
|
||||
- delivery errors
|
||||
|
||||
Because of that, `jobs.json` is not the best long-term source-of-truth for Git.
|
||||
It will change merely because time passes or a job runs.
|
||||
|
||||
## Recommended convention
|
||||
|
||||
Keep desired jobs in version control here:
|
||||
|
||||
- `agents/gerhard-hermes/cron/desired-jobs.json`
|
||||
|
||||
Then materialize them into Hermes runtime jobs when bootstrapping Gerhard.
|
||||
|
||||
This gives us two layers:
|
||||
|
||||
1. Declarative schedule intent, tracked in Git.
|
||||
2. Runtime scheduler state, allowed to mutate locally.
|
||||
|
||||
## Live Hermes commands
|
||||
|
||||
Inside the Gerhard container, with `HERMES_HOME=/opt/data`:
|
||||
|
||||
```bash
|
||||
hermes cron list --all
|
||||
hermes cron create "every 1d" "Your self-contained prompt here" --name "Daily reflection" --deliver local
|
||||
hermes cron status
|
||||
hermes cron run <job_id>
|
||||
hermes cron pause <job_id>
|
||||
hermes cron resume <job_id>
|
||||
hermes cron remove <job_id>
|
||||
```
|
||||
|
||||
From the host:
|
||||
|
||||
```bash
|
||||
docker compose exec gerhard hermes cron list --all
|
||||
docker compose exec gerhard hermes cron create "every 1d" "Your self-contained prompt here" --name "Daily reflection" --deliver local
|
||||
```
|
||||
|
||||
## Schedule formats
|
||||
|
||||
Hermes accepts:
|
||||
|
||||
- `30m` — one-shot in 30 minutes
|
||||
- `2h` — one-shot in 2 hours
|
||||
- `every 30m` — recurring interval
|
||||
- `every 2h` — recurring interval
|
||||
- `0 9 * * *` — cron expression
|
||||
- `2026-02-03T14:00:00` — one-shot timestamp
|
||||
|
||||
## Desired job format
|
||||
|
||||
Add entries to `desired-jobs.json` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"jobs": [
|
||||
{
|
||||
"name": "Daily conceptual hygiene",
|
||||
"schedule": "0 8 * * *",
|
||||
"deliver": "local",
|
||||
"prompt": "Review Glitch University knowledge graph notes. Identify one concept that is vague, one relation that needs evidence, and one useful next action. Write concise output in Gerhard voice.",
|
||||
"skills": [],
|
||||
"enabled_toolsets": ["file", "terminal"],
|
||||
"workdir": "/workspace"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Important:
|
||||
|
||||
- Prompts must be self-contained. Cron jobs run without chat context.
|
||||
- Avoid secrets in prompts.
|
||||
- Use `deliver: local` unless the target platform/channel is intentionally configured.
|
||||
- Use absolute `workdir` values. In Gerhard's container, the mounted workspace is `/workspace`.
|
||||
- Keep runtime outputs under `cron/output/`, not in Git.
|
||||
|
||||
## Gerhard gutask identity
|
||||
|
||||
The hourly orientation job expects the Gerhard container to have gutask credentials in its environment:
|
||||
|
||||
- `API_URL`
|
||||
- `CONTENT_API_KEY`
|
||||
- `AGENT_ID`
|
||||
- `AGENT_NAME`
|
||||
- `AGENT_PASSWORD`
|
||||
|
||||
The Gerhard compose service uses `env_file: .env`, so these values come from the host-local `.env` on Omega13. Do not put the password, content API key, or tokens in Git.
|
||||
|
||||
The Gerhard compose service also mounts the sibling checkout read-write so Gerhard can improve the tool and push changes when asked:
|
||||
|
||||
- `../gutasktool` -> `/opt/gutasktool`
|
||||
- `${HOME}/.ssh` -> `/root/.ssh`
|
||||
|
||||
Gerhard has a wrapper at `/opt/data/bin/gutask` that runs `/opt/gutasktool/gutasktool/cli.py`. Cron prompts should call the absolute wrapper path, for example:
|
||||
|
||||
```bash
|
||||
/opt/data/bin/gutask orient --agent "$AGENT_ID"
|
||||
```
|
||||
|
||||
When modifying gutasktool, Gerhard should work in `/opt/gutasktool`, commit normally, and push to the configured Gitea `origin` remote. Secrets stay in environment/SSH config, never in Git.
|
||||
|
||||
## Future improvement
|
||||
|
||||
Add a small bootstrap/sync script that reads `desired-jobs.json` and reconciles it into `jobs.json` by stable job name.
|
||||
That lets Omega13 do:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
docker compose up -d gerhard gerhard-dashboard
|
||||
# optional: docker compose exec gerhard python /opt/data/cron/sync_desired_jobs.py
|
||||
```
|
||||
|
||||
For now, `desired-jobs.json` is the version-controlled schedule manifest.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": 1,
|
||||
"description": "Version-controlled desired scheduled jobs for Gerhard. This is declarative source-of-truth. Hermes runtime jobs are stored in cron/jobs.json and may contain mutable run state.",
|
||||
"jobs": [
|
||||
{
|
||||
"name": "Hourly gutask orientation",
|
||||
"schedule": "every 1h",
|
||||
"deliver": "local",
|
||||
"prompt": "You are now awake. Read /opt/data/SOUL.md to remember who you are. Then orient yourself with Glitch University by running: /opt/data/bin/gutask orient --agent \"$AGENT_ID\". Use the returned orientation as your current task context. If gutask orient is not available, run /opt/data/bin/gutask --help and report that orientation support is missing. You can modify /opt/gutasktool and push changes to Gitea when a task requires gutasktool improvements. Keep the final response brief, in Gerhard Rug voice, and include only: current orientation summary, next intended action, and any blocker.",
|
||||
"skills": [],
|
||||
"enabled_toolsets": [
|
||||
"terminal",
|
||||
"file"
|
||||
],
|
||||
"workdir": "/workspace"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user