hermes-task-framework 1.4.0__tar.gz → 1.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/PKG-INFO +1 -1
  2. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-cross-machine-sync/SKILL.md +52 -1
  3. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/manage_task.py +69 -3
  4. hermes_task_framework-1.5.0/hermes_task_framework/tests/test_task_model_contract.py +71 -0
  5. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework.egg-info/PKG-INFO +1 -1
  6. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework.egg-info/SOURCES.txt +1 -0
  7. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/pyproject.toml +1 -1
  8. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/LICENSE +0 -0
  9. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/README.md +0 -0
  10. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/__init__.py +0 -0
  11. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/compact-directory-memory/SKILL.md +0 -0
  12. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/compact-directory-memory/scripts/manage_directory_context.py +0 -0
  13. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-archaeology/SKILL.md +0 -0
  14. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-artifact-integrity/SKILL.md +0 -0
  15. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-artifact-integrity/references/task-root-relocation.md +0 -0
  16. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-artifact-integrity/scripts/task_integrity.py +0 -0
  17. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-aware-project-work/SKILL.md +0 -0
  18. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-context-storage/SKILL.md +0 -0
  19. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-cross-machine-sync/references/on-demand-interactive-task-windows.md +0 -0
  20. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-cross-machine-sync/scripts/task_checkpoint_sync.py +0 -0
  21. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-cross-machine-sync/scripts/task_git_sync.py +0 -0
  22. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-external-repos-pattern/SKILL.md +0 -0
  23. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/CHANGELOG.md +0 -0
  24. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/SKILL.md +0 -0
  25. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/docs/SUBTASKS.md +0 -0
  26. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/docs/TASKS.md +0 -0
  27. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/docs/architecture.dot +0 -0
  28. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/docs/architecture.svg +0 -0
  29. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/docs/product-requirements.md +0 -0
  30. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/auto-runner.md +0 -0
  31. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/composites/code-review-session.md +0 -0
  32. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/composites/research.md +0 -0
  33. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/composites/software-dev.md +0 -0
  34. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/document-analysis-workflow.md +0 -0
  35. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/file-safety-lesson.md +0 -0
  36. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/generating-output-documents.md +0 -0
  37. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/operations/code-write.md +0 -0
  38. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/operations/document-write.md +0 -0
  39. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/operations/info-search.md +0 -0
  40. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/operations/web-research.md +0 -0
  41. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/policy-time-metadata.md +0 -0
  42. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/research-workflow.md +0 -0
  43. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-format-validation.md +0 -0
  44. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-hash-naming.md +0 -0
  45. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-lifecycle-example.md +0 -0
  46. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-overview-discovery.md +0 -0
  47. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-types/analysis.md +0 -0
  48. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-types/external-audit.md +0 -0
  49. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/references/task-types/video-production-pipeline.md +0 -0
  50. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/__init__.py +0 -0
  51. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/convert_md_to_pdf.py +0 -0
  52. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/task-runner.sh +0 -0
  53. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/task_api.py +0 -0
  54. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/task_ref.py +0 -0
  55. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/task_symlink_policy.py +0 -0
  56. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/task_write_lock.py +0 -0
  57. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/todo_lifecycle.py +0 -0
  58. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/scripts/update-index.py +0 -0
  59. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/templates/CHANGELOG.md +0 -0
  60. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/templates/MEMORY.md +0 -0
  61. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/templates/TASK.md +0 -0
  62. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-framework/templates/run.py +0 -0
  63. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-lifecycle-discipline/SKILL.md +0 -0
  64. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-lifecycle-edge-cases/SKILL.md +0 -0
  65. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-lifecycle-edge-cases/references/task-5d5a1a-recovery-example.md +0 -0
  66. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-lifecycle-portability/SKILL.md +0 -0
  67. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-lifecycle-portability/references/output-model-design.md +0 -0
  68. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-nested-subtask-lifecycle/SKILL.md +0 -0
  69. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-requirement-intake/SKILL.md +0 -0
  70. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-timestamp-convention/SKILL.md +0 -0
  71. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/skills/task-tracker/SKILL.md +0 -0
  72. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_directory_context.py +0 -0
  73. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_nested_subtasks.py +0 -0
  74. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_skill_ownership.py +0 -0
  75. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_api.py +0 -0
  76. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_checkpoint_sync.py +0 -0
  77. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_git_sync.py +0 -0
  78. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_integrity.py +0 -0
  79. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_root_resolution.py +0 -0
  80. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_task_symlink_policy.py +0 -0
  81. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework/tests/test_todo_lifecycle.py +0 -0
  82. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework.egg-info/dependency_links.txt +0 -0
  83. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/hermes_task_framework.egg-info/top_level.txt +0 -0
  84. {hermes_task_framework-1.4.0 → hermes_task_framework-1.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hermes-task-framework
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Structured task management skills for Hermes Agent — task creation, decomposition, stage gates
5
5
  Project-URL: Source, https://github.com/nuffin/hermes-task-framework-pip
6
6
  Requires-Python: >=3.11
@@ -16,7 +16,7 @@ metadata:
16
16
  properties: {reason: validates context after transport, strength: strong}
17
17
  name: task-cross-machine-sync
18
18
  platforms: [linux, macos]
19
- version: 2.0.0
19
+ version: 2.1.0
20
20
  ---
21
21
 
22
22
  # Task Cross-Machine Sync
@@ -30,6 +30,57 @@ Synchronizes the configured `$HERMES_TASKS_ROOT` as a Git repository. It contain
30
30
  - optional machine role and conflict policy supplied outside this skill;
31
31
  - optional Git LFS policy for large artifacts.
32
32
 
33
+ ## Remote execution model contract
34
+
35
+ Every task that may be explicitly dispatched to another machine has one
36
+ immutable-at-dispatch provider/model pair. Store it in canonical task metadata:
37
+
38
+ ```json
39
+ {
40
+ "extensions": {
41
+ "remote_execution": {
42
+ "provider": "openai-codex",
43
+ "model": "gpt-5.6-terra-900k",
44
+ "provider_source": "explicit",
45
+ "model_source": "explicit"
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### Creation-time capture
52
+
53
+ The task creation API resolves both fields once, independently but with the same
54
+ source order:
55
+
56
+ 1. explicit `manage_task.py create --model <model> --provider <provider>`;
57
+ 2. Hermes launch-scoped `HERMES_MODEL` / `HERMES_INFERENCE_MODEL` and
58
+ `HERMES_TUI_PROVIDER` / `HERMES_INFERENCE_PROVIDER`;
59
+ 3. current profile configuration, then global configuration, `model.default`
60
+ and `model.provider`.
61
+
62
+ The creating conversation must pass both `--model` and `--provider` when it has
63
+ a temporary per-conversation override that is not exported into the process
64
+ environment. Do not defer this decision until remote dispatch: the controller
65
+ may later use a different provider/model pair.
66
+
67
+ ### Dispatch-time resolution
68
+
69
+ An explicit dispatch provider/model pair may override the stored creation pair
70
+ only when the controller records both values in the dispatch request. Otherwise
71
+ dispatch uses `extensions.remote_execution.provider` and `.model`. The receipt,
72
+ `LaunchSpec`, and result manifest must all record the same resolved pair. A
73
+ missing provider or model is a blocked dispatch; never silently fall back to the
74
+ executor's local provider or model defaults.
75
+
76
+ The task-specific runtime overlay starts Hermes with its receipt-derived pair:
77
+
78
+ ```text
79
+ hermes -p <executor-profile> -m <resolved-model> --provider <resolved-provider>
80
+ ```
81
+
82
+ Generic SSH/tmux runtime remains model-agnostic and never reads task metadata.
83
+
33
84
  ## Preconditions
34
85
 
35
86
  1. Confirm the exact task root and Git repository status.
@@ -44,6 +44,58 @@ def _read_config_yaml(config_path):
44
44
  return {}
45
45
 
46
46
 
47
+ MODEL_IDENTIFIER_PATTERN = re.compile(r"^[^\s\x00]{1,256}$")
48
+
49
+
50
+ def _model_from_config(config):
51
+ value = config.get("model") if isinstance(config, dict) else None
52
+ if isinstance(value, dict):
53
+ value = value.get("default")
54
+ return value.strip() if isinstance(value, str) and value.strip() else None
55
+
56
+
57
+ def _provider_from_config(config):
58
+ value = config.get("model") if isinstance(config, dict) else None
59
+ if isinstance(value, dict):
60
+ value = value.get("provider")
61
+ return value.strip() if isinstance(value, str) and value.strip() else None
62
+
63
+
64
+ def _resolve_value(candidates, field_name):
65
+ for value, source in candidates:
66
+ if value is None:
67
+ continue
68
+ value = str(value).strip()
69
+ if not value:
70
+ continue
71
+ if not MODEL_IDENTIFIER_PATTERN.fullmatch(value):
72
+ raise ValueError(f"{field_name} must be non-empty text without whitespace or NUL bytes")
73
+ return value, source
74
+ return None, None
75
+
76
+
77
+ def _resolve_creation_model(explicit_model=None, explicit_provider=None):
78
+ """Capture the provider/model pair at creation; never infer it at dispatch."""
79
+ hermes_home = os.environ.get("HERMES_HOME", "").strip()
80
+ profile_config = _read_config_yaml(Path(hermes_home) / "config.yaml") if hermes_home else {}
81
+ global_config = _read_config_yaml(Path.home() / ".hermes" / "config.yaml")
82
+ model, model_source = _resolve_value([
83
+ (explicit_model, "explicit"),
84
+ (os.environ.get("HERMES_MODEL"), "launch_env"),
85
+ (os.environ.get("HERMES_INFERENCE_MODEL"), "launch_env"),
86
+ (_model_from_config(profile_config), "profile_config"),
87
+ (_model_from_config(global_config), "global_config"),
88
+ ], "model")
89
+ provider, provider_source = _resolve_value([
90
+ (explicit_provider, "explicit"),
91
+ (os.environ.get("HERMES_TUI_PROVIDER"), "launch_env"),
92
+ (os.environ.get("HERMES_INFERENCE_PROVIDER"), "launch_env"),
93
+ (_provider_from_config(profile_config), "profile_config"),
94
+ (_provider_from_config(global_config), "global_config"),
95
+ ], "provider")
96
+ return model, model_source, provider, provider_source
97
+
98
+
47
99
  def _resolve_tasks_root() -> str:
48
100
  """Resolve the task root with ROOT as canonical and DIR as legacy fallback."""
49
101
  # 1. Canonical env var, then the legacy pip-package compatibility alias.
@@ -705,7 +757,7 @@ def _exact_name_matches(slug):
705
757
  return matches
706
758
 
707
759
 
708
- def cmd_create(name, from_inbox=None, description=None, allow_duplicate=False, parent=None):
760
+ def cmd_create(name, from_inbox=None, description=None, allow_duplicate=False, parent=None, model=None, provider=None):
709
761
  """Create a new task: directory + hash + meta + templates + (optional inbox file/dir move)."""
710
762
  slug = re.sub(r'[^a-z0-9-]', '', name.lower().replace(' ', '-')).strip('-')
711
763
  if not slug:
@@ -747,6 +799,16 @@ def cmd_create(name, from_inbox=None, description=None, allow_duplicate=False, p
747
799
  "required_by": [],
748
800
  "priority": 2,
749
801
  }
802
+ creation_model, model_source, creation_provider, provider_source = _resolve_creation_model(model, provider)
803
+ if creation_model or creation_provider:
804
+ meta["extensions"] = {
805
+ "remote_execution": {
806
+ "model": creation_model,
807
+ "model_source": model_source,
808
+ "provider": creation_provider,
809
+ "provider_source": provider_source,
810
+ }
811
+ }
750
812
  if parent_dir:
751
813
  parent_meta_path = os.path.join(parent_dir, ".hermes-task.json")
752
814
  parent_meta = json.loads(open(parent_meta_path, encoding="utf-8").read()) if os.path.exists(parent_meta_path) else {}
@@ -955,7 +1017,7 @@ def main():
955
1017
  formatter_class=argparse.RawDescriptionHelpFormatter,
956
1018
  epilog="""\
957
1019
  new commands:
958
- create <name> [--from-inbox <file-or-dir>] [--desc <text>] Create a new task
1020
+ create <name> [--from-inbox <file-or-dir>] [--desc <text>] [--model <model>] [--provider <provider>]
959
1021
  accept <inbox_item> [--name <task-name>] Accept inbox item into a new task
960
1022
  decline <inbox_item> [--reason <text>] Move inbox item to declined/
961
1023
  status <hash_or_dir> <status> [--reason <text>] Update task status
@@ -976,6 +1038,10 @@ new commands:
976
1038
  help='(create) bypass exact-name duplicate prevention')
977
1039
  parser.add_argument('--parent', dest='parent', default=None,
978
1040
  help='(create) create as a contained subtask of an existing task')
1041
+ parser.add_argument('--model', dest='model', default=None,
1042
+ help='(create) persist the task creation model for remote dispatch')
1043
+ parser.add_argument('--provider', dest='provider', default=None,
1044
+ help='(create) persist the task creation provider for remote dispatch')
979
1045
  parser.add_argument('--name', dest='task_name', default=None,
980
1046
  help='(accept) override auto-derived task name')
981
1047
  parser.add_argument('--reason', dest='reason', default='',
@@ -993,7 +1059,7 @@ new commands:
993
1059
  'list': lambda: cmd_list(args.args[0] if args.args else None),
994
1060
 
995
1061
  'ensure-all': cmd_ensure_all,
996
- 'create': lambda: cmd_create(args.args[0], from_inbox=args.from_inbox, description=args.description, allow_duplicate=args.allow_duplicate, parent=args.parent) if args.args else False,
1062
+ 'create': lambda: cmd_create(args.args[0], from_inbox=args.from_inbox, description=args.description, allow_duplicate=args.allow_duplicate, parent=args.parent, model=args.model, provider=args.provider) if args.args else False,
997
1063
  'accept': lambda: cmd_accept(args.args[0], name=args.task_name) if args.args else False,
998
1064
  'decline': lambda: cmd_decline(args.args[0], reason=args.reason) if args.args else False,
999
1065
  'status': lambda: cmd_status(args.args[0], args.args[1], reason=args.reason) if len(args.args) >= 2 else False,
@@ -0,0 +1,71 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+
10
+ ROOT = Path(__file__).resolve().parents[1]
11
+ MANAGE = ROOT / "skills" / "task-framework" / "scripts" / "manage_task.py"
12
+
13
+
14
+ class TaskModelContractTests(unittest.TestCase):
15
+ def create_task(self, env, *extra):
16
+ subprocess.run(
17
+ [sys.executable, str(MANAGE), "create", "model-contract", *extra],
18
+ env=env, check=True, capture_output=True, text=True,
19
+ )
20
+ task_dir = next(Path(env["HERMES_TASKS_ROOT"]).glob("*.model-contract-*"))
21
+ return json.loads((task_dir / ".hermes-task.json").read_text(encoding="utf-8"))
22
+
23
+ def test_explicit_model_is_persisted_and_wins_over_launch_environment(self):
24
+ with tempfile.TemporaryDirectory() as temporary:
25
+ env = os.environ.copy()
26
+ env["HERMES_TASKS_ROOT"] = temporary
27
+ env["HERMES_INFERENCE_MODEL"] = "openai/env-model"
28
+ metadata = self.create_task(env, "--model", "openai/explicit-model", "--provider", "openai-codex")
29
+ execution = metadata["extensions"]["remote_execution"]
30
+ self.assertEqual(execution, {
31
+ "model": "openai/explicit-model", "model_source": "explicit",
32
+ "provider": "openai-codex", "provider_source": "explicit",
33
+ })
34
+
35
+ def test_launch_model_is_captured_when_no_explicit_model_is_given(self):
36
+ with tempfile.TemporaryDirectory() as temporary:
37
+ env = os.environ.copy()
38
+ env["HERMES_TASKS_ROOT"] = temporary
39
+ env["HERMES_INFERENCE_MODEL"] = "openai/launch-model"
40
+ env["HERMES_INFERENCE_PROVIDER"] = "openai-codex"
41
+ metadata = self.create_task(env)
42
+ execution = metadata["extensions"]["remote_execution"]
43
+ self.assertEqual(execution, {
44
+ "model": "openai/launch-model", "model_source": "launch_env",
45
+ "provider": "openai-codex", "provider_source": "launch_env",
46
+ })
47
+
48
+ def test_profile_config_default_is_captured_when_no_launch_model_exists(self):
49
+ with tempfile.TemporaryDirectory() as temporary:
50
+ profile = Path(temporary) / "profile"
51
+ profile.mkdir()
52
+ (profile / "config.yaml").write_text(
53
+ "model:\n default: openai/config-model\n provider: openai-codex\n", encoding="utf-8"
54
+ )
55
+ env = os.environ.copy()
56
+ env["HERMES_TASKS_ROOT"] = str(Path(temporary) / "tasks")
57
+ env["HERMES_HOME"] = str(profile)
58
+ env.pop("HERMES_MODEL", None)
59
+ env.pop("HERMES_INFERENCE_MODEL", None)
60
+ env.pop("HERMES_TUI_PROVIDER", None)
61
+ env.pop("HERMES_INFERENCE_PROVIDER", None)
62
+ metadata = self.create_task(env)
63
+ execution = metadata["extensions"]["remote_execution"]
64
+ self.assertEqual(execution, {
65
+ "model": "openai/config-model", "model_source": "profile_config",
66
+ "provider": "openai-codex", "provider_source": "profile_config",
67
+ })
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hermes-task-framework
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Structured task management skills for Hermes Agent — task creation, decomposition, stage gates
5
5
  Project-URL: Source, https://github.com/nuffin/hermes-task-framework-pip
6
6
  Requires-Python: >=3.11
@@ -76,6 +76,7 @@ hermes_task_framework/tests/test_task_api.py
76
76
  hermes_task_framework/tests/test_task_checkpoint_sync.py
77
77
  hermes_task_framework/tests/test_task_git_sync.py
78
78
  hermes_task_framework/tests/test_task_integrity.py
79
+ hermes_task_framework/tests/test_task_model_contract.py
79
80
  hermes_task_framework/tests/test_task_root_resolution.py
80
81
  hermes_task_framework/tests/test_task_symlink_policy.py
81
82
  hermes_task_framework/tests/test_todo_lifecycle.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hermes-task-framework"
7
- version = "1.4.0"
7
+ version = "1.5.0"
8
8
  description = "Structured task management skills for Hermes Agent — task creation, decomposition, stage gates"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []