247 Commits
Author SHA1 Message Date
Hare d69c367285 feat: prepare agen crates for publication 2026-08-23 03:41:51 +09:00
Hare aa8dd4f89e test: align workspace server fixtures with current authority 2026-08-23 02:19:36 +09:00
Hare b12e0785e2 fix: migration error 2026-08-23 02:10:03 +09:00
Hare 24291a4545 fix: resolve ticket assignment target integration 2026-08-23 00:23:38 +09:00
Hare 92d073ff36 fix: accept queued lifecycle assignments 2026-08-22 23:58:55 +09:00
Hare bc57ef38c1 fix: guard state change event transitions 2026-08-22 23:48:11 +09:00
Hare ae8a0316d8 fix: guard state field workflow transitions 2026-08-22 23:41:35 +09:00
Hare 2cfbb1caea fix: make role assignment retries idempotent 2026-08-22 23:34:52 +09:00
Hare 703398bd2c fix: reject invalid coder principals safely 2026-08-22 23:27:27 +09:00
Hare 10c80ae514 fix: close ticket start authority gaps 2026-08-22 23:17:44 +09:00
Hare 285763f4ae fix: enforce ticket assignment mutation fences 2026-08-22 23:06:28 +09:00
Hare 8030045602 feat: generalize ticket role assignments 2026-08-22 22:48:56 +09:00
Hare 8c85b93e7d feat: integrate ticket notify delivery warnings 2026-08-22 21:59:21 +09:00
Hare 4017992c7d fix: share ticket query route contracts 2026-08-22 20:20:38 +09:00
Hare 9dc6d8f144 fix: preserve backend product authority boundaries 2026-08-22 20:03:27 +09:00
Hare 779ced82b1 fix: allow workspace clients through ticket REST operations 2026-08-22 19:43:18 +09:00
Hare 30e4985f9a feat: route product commands through backend targets 2026-08-22 19:16:14 +09:00
Hare 1a1e3c286f feat: integrate workspace memory settings authority 2026-08-22 00:34:30 +09:00
Hare 71c906e04d fix: reject legacy workspace worker restore 2026-08-22 00:28:33 +09:00
Hare e6bfb27fa9 fix: make workdir command completion stateful 2026-08-22 00:20:45 +09:00
Hare 2a3ece0364 fix: validate workspace memory settings authority 2026-08-21 22:16:41 +09:00
Hare 61d174b174 fix: enforce workspace memory settings contract 2026-08-21 22:05:53 +09:00
Hare c4814115de feat: bind memory language to workspace settings 2026-08-21 21:50:12 +09:00
Hare f84377b2fe feat: integrate ticket notify delivery warnings 2026-08-21 20:32:26 +09:00
Hare 19f506f8bc fix: restore reviewer ticket and merge request reads 2026-08-21 20:23:26 +09:00
Hare 0a9d09b958 test: cover complete notification delivery failure 2026-08-21 19:19:26 +09:00
Hare 83c6d290b4 fix: complete notification warning context 2026-08-21 19:11:16 +09:00
Hare 831db34acf fix: surface failed Ticket notification delivery 2026-08-21 19:05:53 +09:00
Hare 4a276b0af0 fix: scope console and workspace shell widths 2026-08-21 18:34:40 +09:00
Hare c2b82a2591 fix: contain ticket operation rail layout 2026-08-21 18:30:38 +09:00
Hare 99daaf31b6 fix: widen workspace shell to 920px 2026-08-21 18:08:53 +09:00
Hare 94e51ea6d1 fix: clean up console task summary 2026-08-21 18:06:55 +09:00
Hare f2d2ab0102 fix: center workspace shell at 720px 2026-08-21 18:04:48 +09:00
Hare 6f42f23d2b fix: enlarge overview activity summaries 2026-08-21 17:40:33 +09:00
Hare 8f54fa2a00 fix: remove console composer overhang 2026-08-21 17:37:37 +09:00
Hare c110965911 fix: remove redundant console chrome 2026-08-21 17:26:47 +09:00
Hare 2a48dfc41a fix: set bash output to compact terminal sizing 2026-08-21 15:22:52 +09:00
Hare 0451142d41 fix: align bash sizing with xterm defaults 2026-08-21 15:18:32 +09:00
Hare 65f18b0cdb fix: match bash output to terminal cell sizing 2026-08-21 15:11:15 +09:00
Hare ea31c7ca81 fix: tighten bash terminal line spacing 2026-08-21 13:46:52 +09:00
Hare dd47dba5b0 feat: render bash ANSI colors in web console 2026-08-21 13:36:03 +09:00
Hare cbedc76d06 fix: simplify live bash output presentation 2026-08-21 13:12:50 +09:00
Hare a4aa1a1848 fix: preserve command telemetry through workdir delegation 2026-08-21 13:01:44 +09:00
Hare ee8ee360ef fix: share workspace runtime worker contracts 2026-08-21 12:35:58 +09:00
Hare 72cae33ea6 chore: merge develop into work/companion 2026-08-21 11:38:47 +09:00
Hare 0cd5ca11cc feat: expose merge requests as workspace resources 2026-08-21 11:31:45 +09:00
Hare ccb9d03865 feat: add workspace switcher menu 2026-08-21 11:30:23 +09:00
Hare 41cd2d044a fix: verify resource key schema drift 2026-08-21 10:32:25 +09:00
Hare 85d1815dcf feat: adopt Workspace resource keys 2026-08-21 10:20:32 +09:00
Hare 9989aed916 Merge branch 'work/companion' into develop 2026-08-21 07:21:09 +09:00
Hare 3f2ba9df47 fix: migration error 2026-08-21 07:20:48 +09:00
Hare cd9f0f009e chore: merge develop into work/companion
# Conflicts:
#	crates/tui/src/app.rs
2026-08-21 07:18:07 +09:00
Hare b49abce798 fix: locate multiline Console Tasks source 2026-08-21 07:13:46 +09:00
Hare 51b381a701 feat: switch Web SubWorker views 2026-08-21 07:12:37 +09:00
Hare bea121ade0 feat: switch TUI SubWorker views 2026-08-21 07:12:20 +09:00
Hare 18112d29a6 feat: project SubWorker activity in sidebar 2026-08-21 07:12:04 +09:00
Hare 8bedfcda84 fix: remove caller worker revision fence 2026-08-21 07:11:45 +09:00
Hare 402543d617 Merge remote-tracking branch 'origin/develop' into develop 2026-08-21 05:37:57 +09:00
Hare a21ef31ee7 fix: make authentication workspace independent 2026-08-21 05:04:17 +09:00
Hare e8c159247a fix: serve chooser before workspace creation 2026-08-21 04:51:44 +09:00
Hare 5dd9392575 fix: serve workspace chooser at root 2026-08-21 04:45:05 +09:00
Hare da2296bc9c fix: restore workspace routing contracts 2026-08-21 04:36:40 +09:00
Hare 8e4f35eb3f fix: update TUI command snapshot fixture 2026-08-21 04:21:18 +09:00
Hare 9917c09b19 feat: integrate workspace switching 2026-08-21 04:18:32 +09:00
Hare 4445501f6c fix: recover command snapshots after provider lag 2026-08-21 04:09:42 +09:00
Hare 6fe36f3e46 feat: integrate command output streaming 2026-08-21 03:54:47 +09:00
Hare cfb173c570 fix: preserve command stream boundaries and lifecycle evidence 2026-08-21 03:49:45 +09:00
Hare ad729af592 feat: add web workspace chooser and creation 2026-08-20 18:34:28 +09:00
Hare 17abe1c40c feat: add scoped TUI workspace selection 2026-08-20 18:34:18 +09:00
Hare 4b8dc302ee fix: fence workspace bootstrap and resource routing 2026-08-20 17:52:23 +09:00
Hare cf2e74404d feat: dispatch registered workspaces independently 2026-08-20 17:28:14 +09:00
Hare a0e161c653 feat: integrate workspace-scoped resource references 2026-08-20 16:10:42 +09:00
Hare 614157424f fix: remove cargo check warnings 2026-08-20 15:16:29 +09:00
Hare f5f80fcd48 feat: add web console modes and run status 2026-08-20 15:02:24 +09:00
Hare 7dd4539f50 feat: add compact TUI activity overview 2026-08-20 15:02:03 +09:00
Hare e66876249e fix: remove redundant system reminder wrappers 2026-08-20 15:01:43 +09:00
Hare d39eb43419 fix: track retained assignment parents 2026-08-20 11:38:13 +09:00
Hare 674b897321 fix: validate assignment runtime snapshots 2026-08-20 11:28:41 +09:00
Hare d7e54ed181 fix: preserve retained assignment history 2026-08-20 11:21:31 +09:00
Hare 3e833b5295 fix: finalize stopped subworker sessions 2026-08-20 11:19:02 +09:00
Hare 9194f0a1ba test: complete ticket reference fixture 2026-08-20 11:19:02 +09:00
Hare 6945b7b3c3 fix: bind assignment events to tickets 2026-08-20 11:14:33 +09:00
Hare 560226dea2 fix: enforce assignment workspace references 2026-08-20 11:06:31 +09:00
Hare 4583b512b3 docs: add workspace schema migration runbook 2026-08-20 10:43:05 +09:00
Hare 223a6ed011 feat: enforce workspace-scoped resource references 2026-08-20 10:42:57 +09:00
Hare a82234a75e docs: report subworker feature installation failure 2026-08-20 10:33:12 +09:00
Hare 92594488da feat: stream workdir command output to consoles 2026-08-20 10:32:58 +09:00
Hare 2315c69f0a fix(worker-runtime): complete embedded store identity migration 2026-08-20 08:44:09 +09:00
Hare 9d003a5c98 fix: exclude stale worker sockets from migration 2026-08-20 07:51:34 +09:00
Hare 53ec914a52 docs: report subworker control dependency failure 2026-08-20 07:21:03 +09:00
Hare d052cedc7d fix: make worker identity migrations dry-runnable 2026-08-20 07:21:03 +09:00
Hare de72afd9a1 fix: clear stale internal worker in-flight state 2026-08-20 06:18:21 +09:00
Hare 80ffff642f fix: resolve ticket keys for nested merge request routes 2026-08-20 02:39:52 +09:00
Hare 97960d4e3f fix: resolve human keys at resource boundaries 2026-08-20 02:30:09 +09:00
Hare a96038d79f feat: add canonical human-key resource routes 2026-08-20 02:17:21 +09:00
Hare 1ca36d6b66 feat: add workspace resource human keys 2026-08-20 02:17:00 +09:00
Hare bb8eda379f fix: migrate all persisted Workspace Worker references 2026-08-20 00:49:21 +09:00
Hare 2858e8ceba fix: align Workdir occupancy UI with stable worker IDs 2026-08-19 23:15:34 +09:00
Hare e35b5797a3 feat: promote workers to Workspace-owned UUIDv7 identities 2026-08-19 23:04:02 +09:00
Hare 25baeedc03 Merge branch 'work/companion' into develop 2026-08-19 21:22:04 +09:00
Hare 17d81e29cc chore: merge authoritative develop into ticket source 2026-08-19 12:05:54 +09:00
Hare 08bce5b630 chore: merge current develop into ticket source 2026-08-19 11:54:35 +09:00
Hare 84f1b229ba fix: validate remote workdir grants before leasing 2026-08-19 11:42:49 +09:00
Hare 856ea7119a fix: clarify orchestrator branch integration 2026-08-19 11:24:55 +09:00
Hare 5f2798458e fix: align delegated paths with provider resolution 2026-08-19 11:22:56 +09:00
Hare af3decce51 fix: preserve nested workdir delegation chains 2026-08-19 11:04:40 +09:00
Hare 1cb6cd4e98 fix: enforce delegated workdir scope after resolution 2026-08-19 10:43:37 +09:00
Hare 21bd089a23 feat: delegate subworker access through workdir sessions 2026-08-19 09:45:56 +09:00
Hare 88f463e633 feat: add scoped workdir delegation sessions 2026-08-19 09:45:49 +09:00
Hare ce62e09919 feat: stream internal subworker output through parent 2026-08-19 09:24:14 +09:00
Hare fe74d7c4b8 chore: update ticket source from develop 2026-08-19 08:38:13 +09:00
Hare 73902b03b6 fix: preserve live errors across segment rotation 2026-08-19 08:29:36 +09:00
Hare 9aeaa52bdb fix: retain worker run errors in console history 2026-08-19 08:12:55 +09:00
Hare 89ee5e48a5 fix: use active prompt projection route 2026-08-19 07:03:15 +09:00
Hare 5b5396599d fix: restore pending workspace prompt catalog 2026-08-19 06:52:32 +09:00
Hare 1e33b2945c fix: requeue uncommitted notifications 2026-08-19 06:45:45 +09:00
Hare 50b05051bb feat: persist prompt render provenance 2026-08-19 06:40:29 +09:00
Hare 4208b6228e fix: fence prompt projection freshness 2026-08-19 06:22:41 +09:00
Hare bb558bad2b fix: share compiled prompt projection cache 2026-08-19 06:19:31 +09:00
Hare fcc7c49ff1 fix: enforce prompt projection workspace scope 2026-08-19 06:07:24 +09:00
Hare fb5f49d2a2 test: align pending orchestrator restore contract 2026-08-19 06:05:43 +09:00
Hare 82eaa986d8 fix: scope prompt projection cache fills 2026-08-19 06:03:18 +09:00
Hare 17d6789b41 feat: propagate workspace prompt revisions 2026-08-19 06:01:03 +09:00
Hare d24d50cac9 feat: refresh prompts at operation boundaries 2026-08-19 06:00:57 +09:00
Hare 44b3c78761 fix: reject unrecoverable pending worker restore 2026-08-19 05:02:50 +09:00
Hare 08daf782b9 fix: accept queued tickets after coder spawn 2026-08-19 04:55:29 +09:00
Hare d7e35ea9ee fix: keep restore independent of live prompts 2026-08-19 04:41:38 +09:00
Hare 39aa465a51 fix: keep config bundles transport-only 2026-08-19 04:36:38 +09:00
Hare 382b5e57f2 fix: carry prompt projection source identity 2026-08-19 04:30:00 +09:00
Hare 0d011ea0cd fix: fence prompt cache source revisions 2026-08-19 04:25:13 +09:00
Hare b740b2d1e2 fix: remove config bundle restore dependency 2026-08-19 04:23:37 +09:00
Hare c97bde9ee0 fix: isolate workspace profile launch projections 2026-08-18 22:47:34 +09:00
Hare bb742e253d feat: allow companion to manage workdirs 2026-08-18 10:47:31 +09:00
Hare a10507c54f merge: integrate orchestration
# Conflicts:
#	crates/flow/src/builtin.rs
#	crates/manifest/src/profile.rs
#	crates/worker/src/prompt/catalog.rs
#	crates/worker/src/prompt/system.rs
#	resources/flows/coder-review.dcdl
#	resources/prompts/role/coder.md
#	resources/prompts/role/orchestrator.md
#	web/workspace/src/lib/workspace/console/worker-console.ui.test.ts
#	web/workspace/src/lib/workspace/styles/tickets.css
#	web/workspace/src/routes/w/[workspaceId]/tickets/+page.svelte
#	web/workspace/src/routes/w/[workspaceId]/tickets/+page.ts
2026-08-18 09:55:27 +09:00
Hare 6a607ccbed feat: integrate bounded workspace query pagination 2026-08-18 09:03:59 +09:00
Hare 4ac79b3665 fix: publish coder revisions before review 2026-08-18 08:14:58 +09:00
Hare a4fdf9cc45 docs: record test code quality audit 2026-08-18 08:14:58 +09:00
Hare 981c422122 fix: preserve completed merge request replays 2026-08-18 08:14:43 +09:00
Hare ab0f57c00a fix: include incoming Ticket relation filters 2026-08-18 08:14:05 +09:00
Hare b723c64fa1 fix: preserve accepted Ticket query filters 2026-08-18 08:06:21 +09:00
Hare f86ae6d52f feat: connect selector-based merge request flow 2026-08-18 08:06:02 +09:00
Hare 9a548d2b5e fix: require observed merge target completion 2026-08-18 08:05:57 +09:00
Hare 4e738ac5eb fix: query authoritative merge request review events 2026-08-18 07:57:20 +09:00
Hare a6f3e30652 fix: preserve Ticket lane order across pages 2026-08-18 07:50:42 +09:00
Hare 6ca5dfbe11 feat: paginate Ticket board lanes independently 2026-08-18 03:08:39 +09:00
Hare bc835b8503 feat: add bounded Ticket and Objective query pages 2026-08-18 03:08:30 +09:00
Hare 46767daf49 test: remove duplicated resource content assertions 2026-08-18 02:20:32 +09:00
Hare 99170d47ab fix: delegate queued tickets through merge 2026-08-18 02:20:32 +09:00
Hare 796fa2ee85 feat: integrate ticket ready target guards 2026-08-18 01:12:32 +09:00
Hare 200c24bc00 fix: avoid unbounded ticket board queries 2026-08-18 00:46:31 +09:00
Hare 0a2b24bf5e fix: ignore transient vite config bundles 2026-08-17 23:33:12 +09:00
Hare eec2be87ad feat: paginate ticket workflow lanes 2026-08-17 23:33:12 +09:00
Hare 71cd58f868 feat: scope merge request tools by profile flags 2026-08-17 22:53:09 +09:00
Hare 58ed04ac59 Merge branch 'orchestration' into develop 2026-08-17 21:50:21 +09:00
Hare c6a476d65d feat: derive ticket readiness from merge requests 2026-08-17 21:26:42 +09:00
Hare 4c31ea2228 fix: check queue state before local readiness 2026-08-17 14:59:24 +09:00
Hare f9e5fca67d fix: preserve intake and companion ticket workflows 2026-08-17 14:44:59 +09:00
Hare a2cd860199 feat: gate ticket readiness on validated targets 2026-08-17 14:23:47 +09:00
Hare f5fdce2d07 fix: migrate legacy merge request schema marker 2026-08-17 11:54:24 +09:00
Hare 8c60921c99 chore: merge orchestration into develop 2026-08-17 11:02:47 +09:00
Hare 956b453e6a fix: preserve long-running workdir requests 2026-08-17 11:01:33 +09:00
Hare 063f203efe merge: integrate selector-thread merge request authority 2026-08-17 10:34:01 +09:00
Hare 5f44b10ff9 fix: revalidate merge authority in completion transaction 2026-08-17 08:15:13 +09:00
Hare e3dc8ee327 fix: validate merge authority before ref updates 2026-08-17 08:04:33 +09:00
Hare 645458498a fix: fence merge request approval and grant lifecycle 2026-08-17 07:57:23 +09:00
Hare 6c4119e2c6 fix: separate merge request authority from thread projection 2026-08-17 07:46:48 +09:00
Hare 7e9cae5f39 fix: bound merge request thread bodies 2026-08-17 07:44:08 +09:00
Hare 1fe1b7463f fix: align merge requests with selector thread contract 2026-08-17 07:41:03 +09:00
Hare 9e48cae759 feat: expose selector based merge request threads 2026-08-17 07:09:20 +09:00
Hare aeb2727bea feat: replace merge request revision authority 2026-08-17 07:09:10 +09:00
Hare 298c20012a fix: bind relation and event query evidence 2026-08-17 06:57:29 +09:00
Hare 7507412f1c fix: query authoritative Ticket content and relations 2026-08-17 06:46:43 +09:00
Hare 45b7d0764d fix: complete query and workflow alias cutover 2026-08-17 06:35:24 +09:00
Hare 7508d428b0 docs: report MR target discovery blocker 2026-08-17 05:39:18 +09:00
Hare 977c8e7b21 fix: align read projections with query contracts 2026-08-17 05:37:06 +09:00
Hare 4964583868 feat: cut over Ticket and Objective read tools 2026-08-17 05:18:38 +09:00
Hare 14aa1aabea feat: add bounded Ticket and Objective read APIs 2026-08-17 05:18:27 +09:00
Hare 76c427c887 fix: remove redundant merge request diff digest 2026-08-17 04:13:14 +09:00
Hare ff7d874138 fix: rebuild merge request schema authority 2026-08-17 02:51:53 +09:00
Hare e881c8fad8 fix: remove speculative worker control delegation 2026-08-17 02:51:18 +09:00
Hare 06cc6056e5 chore: merge orchestration into develop 2026-08-17 01:09:51 +09:00
Hare c15999b7a6 fix: classify commit messages by change type 2026-08-17 01:09:12 +09:00
Hare 0b954c1ab6 fix: scope merge request foreign key checks 2026-08-17 01:09:05 +09:00
Hare eb0dd67d16 server: recover completed Worker transfers 2026-08-17 00:48:37 +09:00
Hare 97828f8bd5 server: fence Worker control operation identity 2026-08-17 00:38:48 +09:00
Hare 8be2cfd2a3 worker: unify granted Worker control 2026-08-17 00:18:49 +09:00
Hare f41ab0e277 worker: persist known-worker control grants 2026-08-16 23:08:32 +09:00
Hare 46a44b232b merge-request: normalize target observation refs 2026-08-16 21:03:30 +09:00
Hare 2cf4c57813 merge-request: verify guarded target updates 2026-08-16 21:03:30 +09:00
Hare 41534b215a merge-request: harden target update recovery 2026-08-16 21:03:30 +09:00
Hare 5ea2792df7 merge-request: finalize one guarded merge outcome 2026-08-16 21:03:30 +09:00
Hare 1479148f84 merge-request: select one final integration result 2026-08-16 21:03:30 +09:00
Hare 7256d80514 merge-request: preserve migration foreign keys 2026-08-16 21:03:30 +09:00
Hare 8e73d755d3 merge-request: harden latest schema migration 2026-08-16 21:03:30 +09:00
Hare 027f60d262 merge-request: add target and merge result authority 2026-08-16 21:03:30 +09:00
Hare 4f042cae84 tui: scope composer history under client data 2026-08-16 18:24:34 +09:00
Hare 0b8924eda7 merge-request: checkpoint restartable migration 2026-08-15 21:26:02 +09:00
Hare eaaf2f6dcc workdir: display working directory status 2026-08-15 21:26:02 +09:00
Hare f5a0e14991 docs: require root cargo check 2026-08-15 21:26:02 +09:00
Hare 11e4d536c5 ticket: support relation removal 2026-08-15 17:05:47 +09:00
Hare 0fd2486baf Merge ticket 00001M00PZS9R paused Worker Resume 2026-08-15 03:55:20 +09:00
Hare 8294a476d2 runtime: allow safe paused resume on orchestration 2026-08-15 03:49:53 +09:00
Hare 9922b654d7 Merge ticket 00001KZVPX841 workspace Workdir contract 2026-08-15 03:38:24 +09:00
Hare b910efd945 workdir: centralize workspace inventory contract 2026-08-15 03:29:53 +09:00
Hare 20854edca2 Merge ticket 00001KZ9SR97B notification projection 2026-08-15 02:57:46 +09:00
Hare 2a23a5d574 server: minimize ticket notification context 2026-08-15 02:34:28 +09:00
Hare 4cf34375a8 config: commit without preview 2026-08-15 02:06:46 +09:00
Hare 404809ab6e config: format sources before commit 2026-08-15 01:28:25 +09:00
Hare c5c89795e1 config: require schema assertion on main 2026-08-15 00:21:46 +09:00
Hare 99b08e0f0c web: protect config schema wrapper 2026-08-14 23:38:51 +09:00
Hare 83a99541b2 web: repair config source editor 2026-08-14 23:28:14 +09:00
Hare 1ed835fb79 Merge branch 'orchestration' into develop
# Conflicts:
#	web/workspace/deno.json
2026-08-14 14:49:01 +09:00
Hare ace134e2f0 runtime: deliver immutable prompt bundle on initial spawn 2026-08-14 14:24:59 +09:00
Hare ecbd003579 worker: track work as natural task steps 2026-08-14 14:23:58 +09:00
Hare 93e784a3ed prompt: route source assertions through effective catalog 2026-08-14 14:02:01 +09:00
Hare a1f6a6bad5 prompt: close whitespace-control include validation gap 2026-08-14 13:58:11 +09:00
Hare a6f92104fa server: project Workspace prompts into Worker config bundles 2026-08-14 13:45:14 +09:00
Hare 0ad7d6d210 worker: replace filesystem prompts with effective DCDL catalog 2026-08-14 13:45:03 +09:00
Hare 48ff977d06 config-source: validate static template catalog projections 2026-08-14 13:44:51 +09:00
Hare 93eea24420 web: show Worker tasks in Console 2026-08-14 12:46:59 +09:00
Hare e582babae3 runtime: avoid duplicate Notify observations 2026-08-14 12:46:49 +09:00
Hare 46ffde19a6 config-source: bind import cache keys to content 2026-08-14 12:06:10 +09:00
Hare 6446eb1302 server: project Skills from active virtual config 2026-08-14 11:55:33 +09:00
Hare ad0f6c68d5 config-source: project Markdown imports as values 2026-08-14 11:55:10 +09:00
Hare 6bcd59fcc6 test: cover Decodal 0.4 parity contracts 2026-08-14 11:00:50 +09:00
Hare 1fad5fc8ed config: migrate workspace evaluation to Decodal 0.4 2026-08-14 10:53:22 +09:00
Hare 5c57e10de9 web: link profiles to shared config editor 2026-08-14 10:03:36 +09:00
Hare c0f4e80320 server: preserve profile config authority at spawn 2026-08-14 09:54:11 +09:00
Hare 4857910410 web: retire profile-specific editor 2026-08-14 09:19:13 +09:00
Hare c2510495ef server: derive profiles from virtual config 2026-08-14 09:19:04 +09:00
Hare f8baa1edb7 server: compose Workspace config schemas 2026-08-14 05:38:21 +09:00
Hare 7a8bd7717e server: require main config entrypoint 2026-08-14 04:36:26 +09:00
Hare c98048b97b Merge branch 'work/00001KY8KRJKK-virtual-config-tree' into develop 2026-08-14 03:20:59 +09:00
Hare 38dad4e865 server: authorize orchestrator MR completion 2026-08-14 03:14:08 +09:00
Hare d039359386 web: refine workspace sidebar navigation 2026-08-14 02:20:50 +09:00
Hare 8c68129d69 web: reapply original changes after config conflict 2026-08-14 01:48:36 +09:00
Hare 348ac51011 config: add revision reads and conflict recovery 2026-08-14 01:41:23 +09:00
Hare 544abbbf56 config: enforce browser preflight and generated DTOs 2026-08-14 01:27:29 +09:00
Hare fa9bd8207f config: complete virtual tree editor contract 2026-08-14 01:07:14 +09:00
Hare 0a7e67e373 config: validate every Decodal source before commit 2026-08-14 00:45:20 +09:00
Hare 9cd1c1c448 runtime: update Decodal import loader test 2026-08-14 00:40:39 +09:00
Hare dfea679f0b web: update Decodal CodeMirror baseline 2026-08-14 00:36:39 +09:00
Hare 0689116e4d server: update schema migration expectations 2026-08-14 00:33:00 +09:00
Hare 02a09ae2d7 test: verify browser WASM config parity 2026-08-14 00:31:22 +09:00
Hare 8c6dcb9483 web: edit virtual config sources with WASM core 2026-08-13 22:34:56 +09:00
Hare c8b57a6a5d server: persist evaluated virtual config trees 2026-08-13 22:26:11 +09:00
Hare 7aa4d3067e config: add canonical Decodal source core 2026-08-13 22:13:33 +09:00
471 changed files with 53166 additions and 14106 deletions
+3 -4
View File
@@ -70,11 +70,10 @@ Intake は以下を行う。
利用可能なら、以下の typed Ticket tools を使う。
- `TicketList`: 既存 Ticket の一覧・重複確認。
- `TicketShow`: 関連 Ticket の詳細確認。
- `QueryTicket`: 既存 Ticket の一覧・重複確認。
- `ShowTicket`: 関連 Ticket の詳細確認。
- `TicketCreate`: 合意済み Ticket の作成。
- `TicketComment`: 既存 Ticket refinement / decision / plan の記録。
- `TicketDoctor`: 必要に応じた整合性確認。
Intake は `MergeRequest*`, `TicketWorkflowState`, `TicketClose` を通常使わない。review authority は assigned Coder が起動した read-only direct-child Reviewer の immutable Merge Request attempt に属し、completion / merge / close は各guarded workflowの責務である。
@@ -96,7 +95,7 @@ Ticket tools が利用できない環境では、勝手に file write で代替
### 2. 既存 Ticket を確認する
`TicketList` / `TicketShow` で duplicate / related work を探す。
`QueryTicket` / `ShowTicket` で duplicate / related work を探す。
確認観点:
+12 -11
View File
@@ -36,8 +36,8 @@ TicketCreate / TicketComment
Orchestrator は以下を行う。
- Ticket を `TicketShow` で読む。
- 必要に応じて関連 Ticket を `TicketList` / `TicketShow` で確認する。
- Ticket を `ShowTicket` で読む。
- 必要に応じて関連 Ticket を `QueryTicket` / `ShowTicket` で確認する。
- Ticket body / thread / artifacts / resolution / review / implementation report を読む。
- Ticket が Objective context と結びついている場合は、Objective を medium-term goal / motivation / strategy / success criteria / decision context として読む。ただし Objective context は判断背景であり、Ticket body/thread/artifacts や explicit Ticket relations / OrchestrationPlan records を読む代替ではない。
- repository 状態、関連 docs/code、既存 worktree、visible Pods を必要に応じて明示的に確認する。
@@ -49,6 +49,7 @@ Orchestrator は以下を行う。
- 既存 umbrella/progress-container Ticket が concrete follow-up Ticket / Objective context で置き換え済みなら、superseded/decomposed として退役・close する routing を検討する。
- implementation-ready の場合は `multi-agent-workflow` に渡す `IntentPacket` を作る。
- implementation-ready かつ Ticket が `queued` の場合は、worktree 作成 / implementation Pod `SpawnPod` / coder routing などの side effect の前に、既存の typed Ticket backend/tool path で `queued -> inprogress` を記録する。
- 人間による `ready -> queued` は、記録済み Ticket scopeについて、実装、current MRのguarded merge、completion記録、Ticket closeまでをWorkspace Orchestratorへ委任するdurable gateである。Orchestratorは`queued -> inprogress`を受理した後、current-ref approval、repository evidence、blocking relations、merge CASを確認して完了まで進め、別のmerge確認を待たない。Ticketがseparate approval gateを明記する場合、またはqueued scope外の新しい判断が必要な場合だけ停止する。
- 明示的な queue review 中に、他にも queued Ticket が見え、capacity が空いている場合は、各 Ticket について relation / orchestration-plan / dirty state / visible Pods / worktree / conflict risk を確認し、独立して受理できるものを同じ routing pass で追加の `queued -> inprogress` 候補にする。
- queued Ticket を capacity が見える状態で idle のまま残す場合は、dependency / conflict / capacity / missing planning decision / dirty workspace / reviewer-coder bottleneck / migration boundary / human gate のいずれかに絞った bounded reason を Ticket thread または `TicketOrchestrationPlanRecord` に残す。
- `ready` または `queued` に concrete missing decision / information がある場合だけ、typed state-change/routing event 付きで `planning` に戻す。その event/comment には missing item、checked context、implementation latitude では足りない理由、次の planning question/action を含める。
@@ -61,7 +62,7 @@ Orchestrator は以下を行う。
- 人間/上位 Orchestrator の許可または明示的な routing acceptance なしに coder / reviewer Pod や read-only investigation helper Pod を起動しない。
- unqueued Ticket を capacity 埋めのために開始しない。parallel start の候補は、個別に `queued` であり、人間が routing を許可済みの Ticket に限る。
- 設計境界の未決定を勝手に implementation-ready として固定しない。
- merge / close / cleanup 権限を持たない場面で勝手に完了処理しない
- Ticketが`ready -> queued`されておらず完了権限を委任されていない場合、またはTicketがseparate approval gateを明記する場合に、勝手にmerge / close / cleanupしない。queued delegationとguarded completion evidenceが揃っている場合は、追加のhuman gateを作らず完了まで進める
- Ticket tools があるからといって arbitrary filesystem write を行わない。
- broad multi-Ticket effort のために新しい umbrella/progress-container Ticket を作らない。
- parent/child、sub-ticket、umbrella、part-of、contains などの hierarchy/container relation を split/refinement の代替として扱わない。
@@ -73,21 +74,21 @@ Orchestrator は以下を行う。
利用可能なら、以下を使う。
- `TicketList`: routing 候補関連 Ticket の確認
- `TicketShow`: 対象 Ticket の body / thread / artifacts / resolution / typed relation metadata と derived inverse/blocker view を確認。
- `QueryTicket`: routing 候補関連 Ticket、project-level forward relation (`depends_on` / `blocks` / `related` / `supersedes` / `duplicate_of`) と derived blocker summary を bounded filter/projection で確認する。`depends_on` と incoming unresolved `blocks` は queue/acceptance blocker であり、`related` は blocker ではない。`supersedes` / `duplicate_of` は visible diagnostic として扱い、自動的な lifecycle 変更や scheduler 判断にはしない
- `ShowTicket`: 対象 Ticket の body / thread / artifacts / resolution / typed relation metadata、linked Objective、assignment、implementation/review evidence を確認する
- `TicketComment`: routing decision / intent packet / blocked reason / next question の記録。
- `TicketWorkflowState`: `queued -> inprogress` acceptance、`inprogress -> done`、または concrete missing decision/information reason を伴う `ready|queued -> planning` に使う。
- `TicketRelationQuery`: project-level の forward relation (`depends_on` / `blocks` / `related` / `supersedes` / `duplicate_of`) を読む。`depends_on` と incoming unresolved `blocks` queue/acceptance blocker であり、`related` は blocker ではない。`supersedes` / `duplicate_of` は visible diagnostic として扱い、自動的な lifecycle 変更や scheduler 判断にはしない
- `TicketDependencyCheck`: queue/acceptance 直前の typed dependency readiness guard に使う
- `TicketRelationRecord` / `TicketRelationRemove`: ユーザー合意済みの durable project relation を明示的に更新する場合だけ使う。
- `TicketOrchestrationPlanQuery`: 対象 Ticket や関連 Ticket の ordering / blocker / conflict / waiting-capacity / accepted-plan 記録を読む。queued acceptance 前に必ず確認する。
- `TicketOrchestrationPlanRecord`: Orchestrator が routing 中に project-relevant な ordering / dependency / conflict / capacity/waiting / accepted-plan decision を残す。これは queue reorder、自動起動、state 変更ではない。
- `TicketClose`: 完了権限と resolution が揃っている場合だけ使う。
- `TicketDoctor`: routing 前後の整合性確認。
`TicketCreate` は通常 Intake の責務だが、routing 中に follow-up Ticket が必要だと判断した場合は、ユーザー/上位 Orchestrator の合意後にだけ使う。
## Queued acceptance contract
- `queued -> inprogress` acceptance の直前に `TicketShow` / `TicketRelationQuery` の relation blockers を再確認する。unresolved `depends_on` や incoming unresolved `blocks` が残る場合は implementation side effect を始めず、理由を thread に残して `planning` へ戻すか blocked diagnostic として停止する。
- `queued -> inprogress` acceptance の直前に `ShowTicket` / `QueryTicket` の relation blocker projection を再確認する。unresolved `depends_on` や incoming unresolved `blocks` が残る場合は implementation side effect を始めず、理由を thread に残して `planning` へ戻すか blocked diagnostic として停止する。
- Relation metadata は project-level constraint であり、OrchestrationPlan は runtime ordering/capacity decision である。relation を OrchestrationPlan で代替しないし、OrchestrationPlan を durable dependency authority として扱わない。
`state = queued` は、Ticket が routing 対象として人間により Orchestrator へ渡された状態である。Orchestrator は queued notification を受けたら、Ticket、workspace state、対象 Ticket の `TicketOrchestrationPlanQuery` 記録、risk domain に応じた bounded project context を読んで、次のどちらかを行う。
@@ -100,7 +101,7 @@ Orchestrator は以下を行う。
Parallel acceptance pass:
- 明示的な queue review 中に複数の queued Ticket が見える場合、Orchestrator は最初の1件の完了待ちを default にしない。各 Ticket について Ticket body/thread/artifacts、TicketRelationQuery、TicketOrchestrationPlanQuery、workspace/worktree dirty state、visible Pods、既存 branches、conflict/dependency notes を確認する。
- 明示的な queue review 中に複数の queued Ticket が見える場合、Orchestrator は最初の1件の完了待ちを default にしない。各 Ticket について Ticket body/thread/artifacts、QueryTicket の relation/blocker projection、TicketOrchestrationPlanQuery、workspace/worktree dirty state、visible Pods、既存 branches、conflict/dependency notes を確認する。
- 追加で開始してよいのは、blocking relation/dependency がなく、`do_not_parallelize` または applicable conflict record がなく、source/write surfaces が disjoint または conflict risk が小さく機械的で、coder/reviewer follow-up capacity があり、acceptance basis となる Ticket thread/plan/workspace records を side effect 前に記録・commit でき、別 worktree/branch/scope を切れる Ticket だけである。
- capacity が見えるのに queued Ticket を idle にする場合は、dependency / conflict / capacity / missing planning decision / dirty workspace / reviewer-coder bottleneck / migration boundary / human gate のいずれかの bounded reason を記録する。
- この pass は scheduler、background runner、resource graph solver、automatic queue drain loop ではない。unqueued Ticket を開始せず、各 Ticket の `queued -> inprogress` acceptance を個別に記録する。
@@ -269,8 +270,8 @@ Action:
### 1. 状態確認
- `git state --short --branch`
- `TicketShow <target>`
- 関連 Ticket の `TicketList` / `TicketShow`
- `ShowTicket <target>`
- 関連 Ticket の `QueryTicket` / `ShowTicket`
- 必要に応じて docs/code/workflow/history
- 必要に応じて visible Pods / worktrees / branches
+13 -4
View File
@@ -14,7 +14,7 @@ Workerの状態から純粋に再現可能で、且つ揮発性の無い操作
**禁止**: ターンを跨ぐことができない情報に基づいて、history に記録せずに context だけにコンテンツを差し込むこと。これをやると LLM はそれに反応して生成を行う一方、次以降のターンでhistoryに残らないため、「自分がなぜその発言/tool call をしたか」の根拠が消えるうえ、prompt cache のヒット率も低下させることになる。
新しい input を context に乗せたいなら、必ず先に `worker.history` に append して commit すること。`history.json` への永続化はそこから自動的についてくる。Notify / WorkerEvent / `<system-reminder>`はこの原則で扱う。
新しい input を context に乗せたいなら、必ず先に `worker.history` に append して commit すること。`history.json` への永続化はそこから自動的についてくる。Notify / WorkerEvent / typed `SystemItem` reminder はこの原則で扱う。
また、キャッシュを破壊するタイミングは正確にコントロールされる必要があり、キャッシュ破壊とトークン消費のトレードオフに基づいて慎重に設計されるべきである。
---
@@ -26,17 +26,26 @@ Workerの状態から純粋に再現可能で、且つ揮発性の無い操作
```sh
cargo test -p <crate> --lib <test-or-module-filter>
cargo test -p <crate> --test <test-target> <test-filter>
cargo check -p <crate> -p <dependent-crate>
```
完了前には変更内容に応じて、対象crate全体のtest、影響するfeature構成、依存crateのcheckを追加する。通常の差分検証には以下を使う。
完了前には、workspace rootで必ず`cargo check`を実行する。rootの`cargo check`
`default-members`に含まれるTUIやServerを含む通常のcompile closureを確認するため、公開型の
変更ごとにLLMがreverse dependencyを推測して`-p`を列挙する運用にはしない。
```sh
cargo check
cargo test -p <changed-crate>
cargo fmt --all -- --check
git diff --check HEAD
```
workspace全体、E2E、Nix/Docker buildなどの重い検証は、変更した境界を狭い検証では証明できない場合や明示的に要求された場合に選ぶ。実行した検証が何を証明するのかを意識し、広い検証を形式的に回すだけにしない。
変更したcrate全体のtestに加え、影響するfeature構成やtest-only targetがある場合は、その検証を
追加する。`cargo check`はtestを実行せず、通常有効でないfeatureまでは確認しないため、semanticな
証明とfeature境界の検証はtargeted test/checkで補う。
workspace全体のtest、`--all-targets`、E2E、Nix/Docker buildなどの重い検証は、変更した境界を
通常のroot checkと狭い検証では証明できない場合や、明示的に要求された場合に選ぶ。実行した検証が
何を証明するのかを意識し、広い検証を形式的に回すだけにしない。
---
Generated
+126 -48
View File
@@ -37,6 +37,42 @@ dependencies = [
"cpufeatures 0.2.17",
]
[[package]]
name = "agen"
version = "0.2.1"
dependencies = [
"agen-macros",
"async-trait",
"base64 0.22.1",
"chrono",
"clap",
"dotenv",
"eventsource-stream",
"futures",
"reqwest",
"schemars",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
"tracing-subscriber",
"trybuild",
"wiremock",
"zstd",
]
[[package]]
name = "agen-macros"
version = "0.2.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -539,6 +575,8 @@ dependencies = [
"tokio",
"tokio-tungstenite 0.29.0",
"uuid",
"workdir",
"workspace-api",
]
[[package]]
@@ -589,6 +627,33 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "config-source"
version = "0.1.0"
dependencies = [
"decodal",
"decodal-language-service",
"decodal-language-tools",
"minijinja",
"pretty_assertions",
"serde",
"serde_json",
"serde_yaml",
"sha2 0.11.0",
"thiserror 2.0.18",
"ts-rs",
]
[[package]]
name = "config-source-wasm"
version = "0.1.0"
dependencies = [
"config-source",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
]
[[package]]
name = "const-oid"
version = "0.10.2"
@@ -961,9 +1026,29 @@ checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
[[package]]
name = "decodal"
version = "0.1.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4291c87ce887fafc0acf9f40f4bc17e111457e9d62f1b1530113be6b7a7f1a21"
checksum = "30e2a1ff0bf0d4160b998401a82aef64d26395e5b4798c55fbca6880a52f8e64"
[[package]]
name = "decodal-language-service"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "577f8cdf109dc318c6bef32c95194f0b164ccea234ab0f70a02aeb3296813b16"
dependencies = [
"decodal",
]
[[package]]
name = "decodal-language-tools"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a18ca80b27386c5dbb160c74e88e6c1bfe33fd80f3cd5108bebe800b8aa698c"
dependencies = [
"decodal",
"serde_json",
"wasm-bindgen",
]
[[package]]
name = "deltae"
@@ -2212,42 +2297,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "llm-engine"
version = "0.2.1"
dependencies = [
"async-trait",
"base64 0.22.1",
"chrono",
"clap",
"dotenv",
"eventsource-stream",
"futures",
"llm-engine-macros",
"reqwest",
"schemars",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
"tracing-subscriber",
"trybuild",
"wiremock",
"zstd",
]
[[package]]
name = "llm-engine-macros"
version = "0.2.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -2335,8 +2384,8 @@ dependencies = [
name = "manifest"
version = "0.1.0"
dependencies = [
"agen",
"arc-swap",
"llm-engine",
"protocol",
"secrets",
"serde",
@@ -2463,11 +2512,11 @@ dependencies = [
name = "memory"
version = "0.1.0"
dependencies = [
"agen",
"async-trait",
"chrono",
"libc",
"lint-common",
"llm-engine",
"manifest",
"schemars",
"serde",
@@ -2485,11 +2534,13 @@ dependencies = [
name = "merge-request"
version = "0.1.0"
dependencies = [
"chrono",
"rusqlite",
"serde",
"sha2 0.11.0",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"uuid",
]
[[package]]
@@ -3748,6 +3799,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_cbor_2"
version = "0.13.0"
@@ -3905,10 +3967,10 @@ dependencies = [
name = "session-store"
version = "0.1.0"
dependencies = [
"agen",
"async-trait",
"base64 0.22.1",
"futures",
"llm-engine",
"protocol",
"serde",
"serde_json",
@@ -4346,16 +4408,17 @@ dependencies = [
name = "ticket"
version = "0.1.0"
dependencies = [
"agen",
"async-trait",
"chrono",
"fs4",
"llm-engine",
"project-record",
"rusqlite",
"schemars",
"serde",
"serde_json",
"serde_yaml",
"sha2 0.11.0",
"tempfile",
"thiserror 2.0.18",
"tokio",
@@ -4549,11 +4612,11 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
name = "tools"
version = "0.1.0"
dependencies = [
"agen",
"async-trait",
"filetime",
"fs-operation",
"html5ever",
"llm-engine",
"manifest",
"markup5ever_rcdom",
"pdf-extract",
@@ -4731,13 +4794,12 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
name = "tui"
version = "0.1.0"
dependencies = [
"agen",
"base64 0.22.1",
"client",
"crossterm 0.28.1",
"fs4",
"llm-engine",
"manifest",
"minijinja",
"protocol",
"pulldown-cmark",
"ratatui",
@@ -4751,6 +4813,7 @@ dependencies = [
"toml",
"unicode-width",
"uuid",
"worker",
]
[[package]]
@@ -5997,19 +6060,21 @@ dependencies = [
name = "worker"
version = "0.1.0"
dependencies = [
"agen",
"arc-swap",
"async-trait",
"chrono",
"clap",
"client",
"config-source",
"dotenv",
"flow",
"fs-operation",
"fs4",
"futures",
"futures-util",
"include_dir",
"libc",
"llm-engine",
"manifest",
"mcp",
"memory",
@@ -6043,13 +6108,13 @@ dependencies = [
name = "worker-runtime"
version = "0.1.0"
dependencies = [
"agen",
"async-trait",
"axum",
"base64 0.22.1",
"decodal",
"flow",
"futures",
"llm-engine",
"manifest",
"protocol",
"reqwest",
@@ -6071,6 +6136,16 @@ dependencies = [
"worker",
]
[[package]]
name = "workspace-api"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"ts-rs",
"workdir",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -6141,6 +6216,7 @@ dependencies = [
"toml",
"tui",
"worker",
"workspace-api",
]
[[package]]
@@ -6172,6 +6248,7 @@ dependencies = [
"async-trait",
"axum",
"chrono",
"config-source",
"flow",
"futures",
"manifest",
@@ -6200,6 +6277,7 @@ dependencies = [
"workdir",
"worker",
"worker-runtime",
"workspace-api",
]
[[package]]
+18 -8
View File
@@ -2,8 +2,8 @@
resolver = "2"
members = [
"crates/client",
"crates/llm-engine",
"crates/llm-engine-macros",
"crates/agen",
"crates/agen-macros",
"crates/session-store",
"crates/secrets",
"crates/manifest",
@@ -19,19 +19,22 @@ members = [
"crates/tools",
"crates/fs-operation",
"crates/flow",
"crates/config-source",
"crates/config-source-wasm",
"crates/workdir",
"crates/tui",
"crates/memory",
"crates/ticket",
"crates/merge-request",
"crates/project-record",
"crates/workspace-api",
"crates/workspace-server",
"tests/e2e",
]
default-members = [
"crates/client",
"crates/llm-engine",
"crates/llm-engine-macros",
"crates/agen",
"crates/agen-macros",
"crates/session-store",
"crates/secrets",
"crates/manifest",
@@ -47,12 +50,15 @@ default-members = [
"crates/tools",
"crates/fs-operation",
"crates/flow",
"crates/config-source",
"crates/config-source-wasm",
"crates/workdir",
"crates/tui",
"crates/memory",
"crates/ticket",
"crates/merge-request",
"crates/project-record",
"crates/workspace-api",
"crates/workspace-server",
]
@@ -63,8 +69,8 @@ license = "MIT"
[workspace.dependencies]
# Internal crates
client = { path = "crates/client" }
llm-engine = { path = "crates/llm-engine", version = "0.2" }
llm-engine-macros = { path = "crates/llm-engine-macros", version = "0.2" }
agen = { path = "crates/agen", version = "0.2" }
agen-macros = { path = "crates/agen-macros", version = "0.2" }
manifest = { path = "crates/manifest" }
mcp = { path = "crates/mcp" }
lint-common = { path = "crates/lint-common" }
@@ -74,6 +80,7 @@ ticket = { path = "crates/ticket" }
project-record = { path = "crates/project-record" }
worker = { path = "crates/worker" }
worker-runtime = { path = "crates/worker-runtime" }
workspace-api = { path = "crates/workspace-api" }
yoi-plugin-pdk = { path = "crates/plugin-pdk" }
yoi = { path = "crates/yoi" }
protocol = { path = "crates/protocol" }
@@ -82,6 +89,7 @@ session-analytics = { path = "crates/session-analytics" }
session-store = { path = "crates/session-store" }
secrets = { path = "crates/secrets" }
tools = { path = "crates/tools" }
config-source = { path = "crates/config-source" }
fs-operation = { path = "crates/fs-operation" }
workdir = { path = "crates/workdir" }
tui = { path = "crates/tui" }
@@ -93,7 +101,9 @@ yoi-workspace-server = { path = "crates/workspace-server" }
async-trait = "0.1"
axum = "0.8"
base64 = "0.22.1"
decodal = "0.1.1"
decodal = "0.4.0"
decodal-language-service = "0.4.0"
decodal-language-tools = "0.4.0"
fs4 = "0.13"
futures = "0.3"
libc = "0.2"
@@ -102,7 +112,7 @@ serde = "1.0"
serde_json = "1.0"
serde_yaml = "0.9.34"
tar = "0.4"
rusqlite = { version = "0.37", features = ["bundled"] }
rusqlite = { version = "0.37", features = ["backup", "bundled"] }
ring = "0.17.14"
sha2 = "0.11"
tempfile = "3.27"
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "agen-macros"
description = "Procedural macros for declaring agen tools"
version = "0.2.0"
edition.workspace = true
rust-version = "1.85"
license.workspace = true
readme = "README.md"
repository = "https://gitea.hareworks.net/Hare/yoi"
homepage = "https://gitea.hareworks.net/Hare/yoi"
documentation = "https://docs.rs/agen-macros"
keywords = ["llm", "agent", "tools", "macros"]
categories = ["development-tools::procedural-macro-helpers"]
include = ["src/**", "README.md", "LICENSE"]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
[package.metadata.docs.rs]
all-features = true
+7
View File
@@ -0,0 +1,7 @@
Copyright 2026 Hare
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.
+32
View File
@@ -0,0 +1,32 @@
# agen-macros
Procedural macros used by [`agen`](https://crates.io/crates/agen) to declare LLM tools from Rust methods.
Applications should normally depend only on `agen` and import its re-exports:
```rust
use agen::tool_registry;
#[derive(Clone)]
struct Tools;
#[tool_registry]
impl Tools {
/// Returns the supplied text.
#[tool]
async fn echo(
&self,
#[description = "Text to return"] text: String,
) -> Result<String, std::io::Error> {
Ok(text)
}
}
```
`#[tool_registry]` generates the argument schema, a `Tool` implementation, and an `<method>_definition` constructor. It rejects arguments of its own, duplicate `#[tool]` markers, malformed or duplicate `#[description = "..."]` attributes, and non-identifier argument patterns.
Generated code targets the canonical `::agen` path and uses implementation dependencies re-exported by `agen`; consumers do not need direct `serde`, `schemars`, `serde_json`, or `async-trait` dependencies. Renaming the `agen` dependency in `Cargo.toml` is not currently supported.
This companion package is published before the matching `agen` release. Its public contract is the generated API consumed by `agen`, and its minor version compatibility follows the `agen` 0.2 series.
Licensed under the [MIT License](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/LICENSE).
+482
View File
@@ -0,0 +1,482 @@
//! Procedural macros for declaring [`agen`](https://docs.rs/agen) tools.
//!
//! [`tool_registry`] expands methods marked with `#[tool]` into `agen::tool::Tool`
//! implementations and tool definitions. Applications normally use the re-exports from
//! `agen`; this companion crate exists so those macros can be published and versioned
//! independently.
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
Attribute, FnArg, ImplItem, ItemImpl, Lit, Meta, Pat, ReturnType, Type, parse_macro_input,
spanned::Spanned,
};
/// Generates tools for methods marked with `#[tool]` in an `impl` block.
///
/// Method doc comments become the tool description. An argument can use
/// `#[description = "..."]` to supply its JSON Schema description.
///
/// ```ignore
/// #[derive(Clone)]
/// struct MyApp;
///
/// #[agen::tool_registry]
/// impl MyApp {
/// /// Retrieves a user by ID.
/// #[tool]
/// async fn get_user(
/// &self,
/// #[description = "The user ID"] user_id: String,
/// ) -> Result<String, std::io::Error> {
/// todo!()
/// }
/// }
/// ```
///
/// This generates a `ToolGetUser` wrapper, a `GetUserArgs` schema type, and
/// `MyApp::get_user_definition()`.
#[proc_macro_attribute]
pub fn tool_registry(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = proc_macro2::TokenStream::from(attr);
let impl_block = parse_macro_input!(item as ItemImpl);
expand_tool_registry(attr, impl_block)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn expand_tool_registry(
attr: proc_macro2::TokenStream,
mut impl_block: ItemImpl,
) -> syn::Result<proc_macro2::TokenStream> {
if !attr.is_empty() {
return Err(syn::Error::new(
attr.span(),
"tool_registry does not accept arguments",
));
}
let self_ty = impl_block.self_ty.as_ref().clone();
let mut generated_items = Vec::new();
for item in &mut impl_block.items {
let ImplItem::Fn(method) = item else {
continue;
};
let tool_attrs: Vec<_> = method
.attrs
.iter()
.filter(|attr| attr.path().is_ident("tool"))
.collect();
if tool_attrs.len() > 1 {
return Err(syn::Error::new_spanned(
tool_attrs[1],
"duplicate #[tool] attribute",
));
}
let Some(tool_attr) = tool_attrs.first() else {
continue;
};
if !matches!(tool_attr.meta, Meta::Path(_)) {
return Err(syn::Error::new_spanned(
tool_attr,
"#[tool] does not accept arguments",
));
}
method.attrs.retain(|attr| !attr.path().is_ident("tool"));
generated_items.push(generate_tool_impl(&self_ty, method)?);
for input in &mut method.sig.inputs {
if let FnArg::Typed(pat_type) = input {
pat_type
.attrs
.retain(|attr| !attr.path().is_ident("description"));
}
}
}
Ok(quote! {
#impl_block
#(#generated_items)*
})
}
fn extract_doc_comment(attrs: &[Attribute]) -> String {
let mut lines = Vec::new();
for attr in attrs {
if attr.path().is_ident("doc")
&& let Meta::NameValue(meta) = &attr.meta
&& let syn::Expr::Lit(expr_lit) = &meta.value
&& let Lit::Str(lit_str) = &expr_lit.lit
{
let line = lit_str.value();
let trimmed = line.strip_prefix(' ').unwrap_or(&line);
lines.push(trimmed.to_string());
}
}
lines.join("\n")
}
fn extract_description_attr(attrs: &[Attribute]) -> syn::Result<Option<String>> {
let mut description = None;
for attr in attrs
.iter()
.filter(|attr| attr.path().is_ident("description"))
{
let value = match &attr.meta {
Meta::NameValue(meta) => match &meta.value {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
Lit::Str(value) => value.value(),
_ => {
return Err(syn::Error::new_spanned(
attr,
"description must be a string literal",
));
}
},
_ => {
return Err(syn::Error::new_spanned(
attr,
"description must be a string literal",
));
}
},
_ => {
return Err(syn::Error::new_spanned(
attr,
"expected #[description = \"...\"]",
));
}
};
if description.replace(value).is_some() {
return Err(syn::Error::new_spanned(
attr,
"duplicate #[description] attribute",
));
}
}
Ok(description)
}
fn argument_ident(pat: &Pat) -> syn::Result<&syn::Ident> {
match pat {
Pat::Ident(pat_ident) => Ok(&pat_ident.ident),
_ => Err(syn::Error::new_spanned(
pat,
"tool arguments must use simple identifier patterns",
)),
}
}
fn is_tool_execution_context_type(ty: &Type) -> bool {
let Type::Path(path) = ty else {
return false;
};
path.path
.segments
.last()
.is_some_and(|segment| segment.ident == "ToolExecutionContext")
}
fn generate_tool_impl(
self_ty: &Type,
method: &syn::ImplItemFn,
) -> syn::Result<proc_macro2::TokenStream> {
let sig = &method.sig;
let method_name = &sig.ident;
let tool_name = method_name.to_string();
let pascal_name = to_pascal_case(&method_name.to_string());
let tool_struct_name = format_ident!("Tool{}", pascal_name);
let args_struct_name = format_ident!("{}Args", pascal_name);
let definition_name = format_ident!("{}_definition", method_name);
let description = extract_doc_comment(&method.attrs);
let description = if description.is_empty() {
format!("Tool: {}", tool_name)
} else {
description
};
let method_args: Vec<_> = sig
.inputs
.iter()
.filter_map(|arg| match arg {
FnArg::Typed(pat_type) => Some(pat_type),
FnArg::Receiver(_) => None,
})
.collect();
let json_args: Vec<_> = method_args
.iter()
.copied()
.filter(|pat_type| !is_tool_execution_context_type(pat_type.ty.as_ref()))
.collect();
let arg_fields: Vec<_> = json_args
.iter()
.map(|pat_type| {
let field_name = argument_ident(pat_type.pat.as_ref())?;
let ty = &pat_type.ty;
let description = extract_description_attr(&pat_type.attrs)?;
Ok(if let Some(description) = description {
quote! {
#[schemars(description = #description)]
pub #field_name: #ty
}
} else {
quote! {
pub #field_name: #ty
}
})
})
.collect::<syn::Result<_>>()?;
let call_args: Vec<_> = method_args
.iter()
.map(|pat_type| {
if is_tool_execution_context_type(pat_type.ty.as_ref()) {
Ok(quote! { ctx.clone() })
} else {
let ident = argument_ident(pat_type.pat.as_ref())?;
Ok(quote! { args.#ident })
}
})
.collect::<syn::Result<_>>()?;
let method_call = if call_args.is_empty() {
quote! { self.ctx.#method_name() }
} else {
quote! { self.ctx.#method_name(#(#call_args),*) }
};
let awaiter = if sig.asyncness.is_some() {
quote! { .await }
} else {
quote! {}
};
let result_handling = if is_result_type(&sig.output) {
quote! {
match result {
Ok(val) => Ok(format!("{:?}", val).into()),
Err(error) => Err(::agen::tool::ToolError::ExecutionFailed(format!("{}", error))),
}
}
} else {
quote! {
Ok(format!("{:?}", result).into())
}
};
let args_struct_def = quote! {
#[derive(
::agen::__private::serde::Deserialize,
::agen::__private::schemars::JsonSchema,
)]
#[serde(crate = "::agen::__private::serde")]
#[schemars(crate = "::agen::__private::schemars")]
struct #args_struct_name {
#(#arg_fields),*
}
};
let execute_body = if json_args.is_empty() {
quote! {
let _: #args_struct_name = ::agen::__private::serde_json::from_str(input_json)
.unwrap_or(#args_struct_name {});
let result = #method_call #awaiter;
#result_handling
}
} else {
quote! {
let args: #args_struct_name = ::agen::__private::serde_json::from_str(input_json)
.map_err(|error| ::agen::tool::ToolError::InvalidArgument(error.to_string()))?;
let result = #method_call #awaiter;
#result_handling
}
};
Ok(quote! {
#args_struct_def
#[derive(Clone)]
pub struct #tool_struct_name {
ctx: #self_ty,
}
#[::agen::__private::async_trait::async_trait]
impl ::agen::tool::Tool for #tool_struct_name {
async fn execute(
&self,
input_json: &str,
ctx: ::agen::tool::ToolExecutionContext,
) -> Result<::agen::tool::ToolOutput, ::agen::tool::ToolError> {
let _ = &ctx;
#execute_body
}
}
impl #self_ty {
/// Returns a tool definition for registration with an `agen::Engine`.
pub fn #definition_name(&self) -> ::agen::tool::ToolDefinition {
let ctx = self.clone();
::std::sync::Arc::new(move || {
let schema = ::agen::__private::schemars::schema_for!(#args_struct_name);
let meta = ::agen::tool::ToolMeta::new(#tool_name)
.description(#description)
.input_schema(
::agen::__private::serde_json::to_value(schema)
.unwrap_or_else(|_| ::agen::__private::serde_json::json!({})),
);
let tool: ::std::sync::Arc<dyn ::agen::tool::Tool> =
::std::sync::Arc::new(#tool_struct_name { ctx: ctx.clone() });
(meta, tool)
})
}
}
})
}
fn is_result_type(return_type: &ReturnType) -> bool {
match return_type {
ReturnType::Default => false,
ReturnType::Type(_, ty) => {
if let Type::Path(type_path) = ty.as_ref()
&& let Some(segment) = type_path.path.segments.last()
{
return segment.ident == "Result";
}
false
}
}
}
fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
})
.collect()
}
/// Marker attribute interpreted by [`tool_registry`].
#[proc_macro_attribute]
pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
marker_attribute("tool", attr, item)
}
/// Argument description marker interpreted by [`tool_registry`].
///
/// Use it as `#[description = "The argument description"]` on a tool method argument.
#[proc_macro_attribute]
pub fn description(attr: TokenStream, item: TokenStream) -> TokenStream {
marker_attribute("description", attr, item)
}
fn marker_attribute(name: &str, attr: TokenStream, item: TokenStream) -> TokenStream {
if attr.is_empty() {
item
} else {
syn::Error::new(
proc_macro2::Span::call_site(),
format!("{name} is a marker interpreted by #[tool_registry]"),
)
.into_compile_error()
.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use syn::parse_quote;
#[test]
fn rejects_tool_registry_arguments() {
let implementation: ItemImpl = parse_quote!(impl Registry {});
let error = expand_tool_registry(quote!(unexpected), implementation).unwrap_err();
assert!(error.to_string().contains("does not accept arguments"));
}
#[test]
fn rejects_duplicate_tool_markers() {
let implementation: ItemImpl = parse_quote! {
impl Registry {
#[tool]
#[tool]
fn inspect(&self) {}
}
};
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
assert!(error.to_string().contains("duplicate #[tool]"));
}
#[test]
fn rejects_invalid_description_attributes() {
let implementation: ItemImpl = parse_quote! {
impl Registry {
#[tool]
fn inspect(&self, #[description] input: String) {}
}
};
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
assert!(error.to_string().contains("expected #[description"));
}
#[test]
fn rejects_duplicate_description_attributes() {
let implementation: ItemImpl = parse_quote! {
impl Registry {
#[tool]
fn inspect(
&self,
#[description = "first"]
#[description = "second"]
input: String,
) {}
}
};
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
assert!(error.to_string().contains("duplicate #[description]"));
}
#[test]
fn generated_code_uses_only_agen_runtime_paths() {
let implementation: ItemImpl = parse_quote! {
impl Registry {
#[tool]
fn inspect(&self, input: String) -> Result<String, Error> {
unreachable!()
}
}
};
let expanded = expand_tool_registry(quote!(), implementation)
.unwrap()
.to_string();
assert!(expanded.contains(":: agen :: tool :: Tool"));
assert!(expanded.contains(":: agen :: __private :: serde_json"));
assert!(expanded.contains(":: agen :: __private :: serde"));
assert!(expanded.contains(":: agen :: __private :: schemars"));
}
}
@@ -1,9 +1,18 @@
[package]
name = "llm-engine"
description = "A library for building autonomous LLM-powered systems"
name = "agen"
description = "Provider-neutral orchestration for tool-using LLM applications"
version = "0.2.1"
edition.workspace = true
rust-version = "1.86"
license.workspace = true
readme = "README.md"
repository = "https://gitea.hareworks.net/Hare/yoi"
homepage = "https://gitea.hareworks.net/Hare/yoi"
documentation = "https://docs.rs/agen"
keywords = ["llm", "agent", "tools", "streaming", "orchestration"]
categories = ["api-bindings", "asynchronous"]
include = ["src/**", "tests/**", "examples/*.rs", "docs/**", "README.md", "LICENSE"]
autoexamples = false
[features]
default = []
@@ -12,6 +21,7 @@ codex = ["dep:chrono"]
[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
schemars = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
async-trait = { workspace = true }
@@ -23,13 +33,23 @@ eventsource-stream = "0.2"
zstd = "0.13"
base64 = "0.22.1"
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"], optional = true }
llm-engine-macros = { workspace = true }
agen-macros = { workspace = true }
[dev-dependencies]
clap = { version = "4.5", features = ["derive", "env"] }
schemars = { workspace = true }
tempfile = { workspace = true }
dotenv = "0.15"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
trybuild = "1.0.116"
wiremock = "0.6.5"
[[example]]
name = "engine_cancel_demo"
path = "examples/engine_cancel_demo.rs"
[[example]]
name = "engine_cli"
path = "examples/engine_cli.rs"
[package.metadata.docs.rs]
all-features = true
+7
View File
@@ -0,0 +1,7 @@
Copyright 2026 Hare
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.
+90
View File
@@ -0,0 +1,90 @@
# agen
`agen` is a provider-neutral Rust engine for streaming LLM applications that use tools. It owns the turn loop, typed conversation history, provider wire-format adapters, tool execution, interceptors, usage accounting, and cache-aware state transitions.
> `agen` is pre-1.0. Public APIs may change between minor releases.
## Installation
```toml
[dependencies]
agen = "0.2.1"
```
The default feature set is intentionally empty. Enable the experimental Codex/ChatGPT authentication adapter when needed:
```toml
agen = { version = "0.2.1", features = ["codex"] }
```
`agen` requires Rust 1.86 or newer. The companion `agen-macros` package requires Rust 1.85 or newer.
## Quick start
Supply an implementation of [`LlmClient`](https://docs.rs/agen/latest/agen/llm_client/trait.LlmClient.html), then run a turn. The first call consumes the mutable engine and returns a cache-locked engine for later turns.
```no_run
use agen::{Engine, EngineError};
use agen::llm_client::LlmClient;
async fn conversation<C: LlmClient>(client: C) -> Result<(), EngineError> {
let output = Engine::new(client)
.system_prompt("You are a concise assistant.")
.run("Explain typed state in one sentence.")
.await?;
let mut engine = output.engine;
let _result = engine.run("Give a Rust example.").await?;
Ok(())
}
```
## Declaring tools
The tool macros are re-exported by `agen`; applications do not need direct dependencies on `serde`, `schemars`, `serde_json`, or `async-trait` for generated code.
```rust
use agen::tool_registry;
#[derive(Clone)]
struct Tools;
#[tool_registry]
impl Tools {
/// Returns the supplied text.
#[tool]
async fn echo(
&self,
#[description = "Text to return"] text: String,
) -> Result<String, std::io::Error> {
Ok(text)
}
}
let definition = Tools.echo_definition();
assert_eq!(definition().0.name, "echo");
```
The generated API uses the canonical crate name `agen`. Renaming the `agen` dependency in `Cargo.toml` is not currently supported by these macros.
## Features
| Feature | Default | Adds |
|---|---:|---|
| `codex` | No | Experimental Codex/ChatGPT auth-file loading and token refresh support |
The base crate includes provider-neutral transport and Anthropic, OpenAI-compatible, Gemini, and Ollama wire-format schemes. See [`llm_client`](https://docs.rs/agen/latest/agen/llm_client/) for the client boundary.
## Architecture and API scope
The current public modules cover the engine, typed history, client transport/schemes, timeline events, tools, interceptors, pruning, token estimation, and usage records. Their relationships are described in [Architecture](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/crates/agen/docs/architecture.md); behavioral requirements are summarized in [Requirements](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/crates/agen/docs/requirements.md).
Low-level modules remain public in the 0.2 series because downstream Yoi components implement custom clients, event handlers, pruning policies, and tool registries against them. This surface is versioned as pre-1.0 API rather than declared stable.
## Packaging and security
The published package contains source, public documentation, curated examples, and deterministic tests/fixtures. Credentialed fixture-recording utilities are intentionally excluded. Examples that contact a provider read credentials from environment variables and never embed production credentials.
## License
Licensed under the [MIT License](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/LICENSE).
+62
View File
@@ -0,0 +1,62 @@
# agen architecture
`agen` separates orchestration, event projection, and provider transport so applications can replace an LLM client without changing the turn loop or tool model.
```text
┌────────────────────────────────────────────┐
│ Engine │
│ turn loop · interceptors · tool execution │
│ typed state: Mutable → Locked → Mutable │
└─────────────────────┬──────────────────────┘
┌─────────────────────▼──────────────────────┐
│ Timeline │
│ event dispatch · block collectors │
└─────────────────────┬──────────────────────┘
┌─────────────────────▼──────────────────────┐
│ LlmClient │
│ transport · provider wire-format schemes │
└────────────────────────────────────────────┘
```
## Main modules
| Module | Responsibility |
|---|---|
| `engine` | Turn execution, pause/resume, retries, tool integration, and callbacks |
| `state` | Sealed `Mutable` and `Locked` type-state markers |
| `interceptor` | Application-owned control decisions at orchestration boundaries |
| `tool` / `tool_server` | Tool metadata, registration, execution, and bounded output |
| `timeline` | Streaming event dispatch, handlers, and block assembly |
| `llm_client` | Provider-neutral request, response, auth, transport, and scheme contracts |
| `providers` | Optional higher-level provider adapters such as the `codex` feature |
| `prune` / `token_counter` | Cache-aware history reduction and token estimation |
| `usage_record` | Request and token usage accounting |
## Request flow
```text
Engine history
→ provider-neutral Request
→ Scheme::build_request
→ Provider transport
```
## Response flow
```text
streaming response bytes
→ Scheme event parsing
→ unified Event values
→ Timeline handlers and collectors
→ Engine history/tool decisions
```
## Type state and cache protection
`Engine<C, Mutable>` permits configuration and history editing. `Engine::run` or `Engine::lock` commits the current prefix and produces `Engine<C, Locked>`. The locked engine may append turns without mutating the committed prefix. `Engine::unlock` explicitly returns to mutable state when an application accepts losing that cache guarantee.
## Public surface
The 0.2 series exposes the low-level client, timeline, tool, pruning, and usage modules because custom clients and orchestration hosts build directly on them. These APIs are intentionally provider-neutral but remain pre-1.0 and may change in later minor releases.
+39
View File
@@ -0,0 +1,39 @@
# agen requirements
## R1: Turn execution and continuation
- `Engine::run` starts a turn and loops through provider output and tool calls.
- An `Interceptor` may continue, cancel, or pause work at defined orchestration boundaries.
- `Engine::resume` continues paused generation without fabricating another user message.
- Cancellation and provider errors are represented as typed `EngineError` values.
## R2: Explicit cache-preserving state
- `Engine<C, Mutable>` permits configuration and history edits.
- `Engine::run` or `Engine::lock` transitions to `Engine<C, Locked>` and records the committed prefix.
- A locked engine appends turns but cannot mutate that prefix through mutable-only APIs.
- `Engine::unlock` explicitly abandons the lock before configuration or history changes.
## R3: Tool declarations and execution
- `#[tool_registry]` generates a schema and `Tool` implementation for methods marked `#[tool]`.
- `#[description = "..."]` supplies argument descriptions in generated JSON Schema.
- Generated code resolves its runtime and helper dependencies through `::agen`.
- Invalid and duplicate marker attributes produce compile errors rather than panics.
- Tools execute through `ToolServer` with typed context, errors, and output limits.
## R4: Provider-neutral orchestration
- `LlmClient` is the boundary between the engine and provider-specific transport.
- Request/response schemes translate provider wire formats into shared request and event types.
- Interceptors, tool execution, timeline collection, and pruning stay above the provider transport.
- Provider-specific capabilities are optional features when they require additional policy or dependencies.
## R5: Publication quality
- crates.io metadata includes license, repository, documentation, README, categories, keywords, and MSRV.
- The default feature set and each optional feature compile and test independently.
- Macro expansion compiles in a downstream-style integration test without direct helper dependencies.
- rustdoc builds without dependency documentation.
- Package contents are explicitly bounded and exclude credentialed fixture-recording utilities.
- `cargo package` and `cargo publish --dry-run` are run for `agen-macros` before `agen` because the main package depends on its companion package.
@@ -2,9 +2,9 @@
//!
//! Example of cancelling from another thread during streaming
use llm_engine::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::{Engine, EngineResult};
use agen::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
use agen::llm_client::transport::{HttpTransport, ResolvedAuth};
use agen::{Engine, EngineResult};
use std::time::Duration;
#[tokio::main]
@@ -38,8 +38,7 @@ use async_trait::async_trait;
use tracing::info;
use tracing_subscriber::EnvFilter;
use clap::{Parser, ValueEnum};
use llm_engine::{
use agen::{
Engine,
interceptor::{Interceptor, PostToolAction, ToolResultInfo},
llm_client::{
@@ -51,12 +50,9 @@ use llm_engine::{
transport::{HttpTransport, ResolvedAuth},
},
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
tool_registry,
};
use llm_engine_macros::tool_registry;
// Required imports for macro expansion
use schemars;
use serde;
use clap::{Parser, ValueEnum};
// =============================================================================
// Provider Definition
@@ -19,11 +19,11 @@
mod recorder;
mod scenarios;
use clap::{Parser, ValueEnum};
use llm_engine::llm_client::scheme::{
use agen::llm_client::scheme::{
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
};
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use agen::llm_client::transport::{HttpTransport, ResolvedAuth};
use clap::{Parser, ValueEnum};
fn make_transport<S: Scheme>(scheme: S, model: &str, auth: ResolvedAuth) -> HttpTransport<S> {
let cap = scheme.default_capability();
@@ -7,8 +7,8 @@ use std::io::{BufWriter, Write};
use std::path::Path;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use agen::llm_client::{LlmClient, Request};
use futures::StreamExt;
use llm_engine::llm_client::{LlmClient, Request};
/// Recorded event
#[derive(Debug, serde::Serialize, serde::Deserialize)]
@@ -2,7 +2,7 @@
//!
//! Defines requests and output file names for each scenario
use llm_engine::llm_client::{Request, ToolDefinition};
use agen::llm_client::{Request, ToolDefinition};
/// Test scenario
pub struct TestScenario {
@@ -88,7 +88,7 @@ pub enum EngineResult {
Yielded,
}
/// Result of [`Engine<C, Mutable>::run()`] / [`Engine<C, Mutable>::resume()`].
/// Result of [`Engine::run`] or [`Engine::resume`].
///
/// Contains the `Locked` Engine (ready for subsequent runs) and the outcome.
pub struct EngineRunOutput<C: LlmClient> {
@@ -181,7 +181,7 @@ pub struct Engine<C: LlmClient, S: EngineState = Mutable> {
locked_prefix_len: usize,
/// AgentTurn count.
///
/// Once retry (`llm-engine-stream-continuation`) is implemented, an
/// Once retry (`agen-stream-continuation`) is implemented, an
/// AgentTurn collapses N retried `LlmCall`s with identical input;
/// today retry is not implemented so AgentTurn and LlmCall fire 1:1
/// and the increment site (the LLM-call loop) is shared.
@@ -1242,7 +1242,7 @@ impl<C: LlmClient, S: EngineState> Engine<C, S> {
}
// LlmCall boundary fires per LLM generation request — today
// 1:1 with AgentTurn, but retry (`llm-engine-stream-continuation`)
// 1:1 with AgentTurn, but retry (`agen-stream-continuation`)
// will multiply this within a single AgentTurn.
let current_llm_call = self.llm_call_count;
for cb in &self.llm_call_start_cbs {
@@ -1696,8 +1696,7 @@ impl<C: LlmClient> Engine<C, Mutable> {
/// Register a tool factory for deferred initialization.
///
/// The factory is queued and executed at the next `run()` or `resume()` call.
/// Duplicate name detection occurs at that point and surfaces as
/// [`EngineError::ToolRegistry`].
/// Duplicate name detection occurs when pending tools are flushed before that call.
pub fn register_tool(&mut self, factory: EngineToolDefinition) {
self.tool_server.register_tool(factory);
}
@@ -1880,7 +1879,7 @@ impl<C: LlmClient> Engine<C, Mutable> {
/// This is the primary entry point for first use. Equivalent to
/// `self.lock()` followed by `locked.run(user_input)`.
///
/// Subsequent runs can use [`Engine<C, Locked>::run()`] directly.
/// Subsequent runs can call [`Engine::run`] directly.
/// To edit state between turns, call [`unlock()`](Engine::unlock) first.
pub async fn run(
self,
@@ -32,7 +32,7 @@ pub trait Kind {
/// # Examples
///
/// ```ignore
/// use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind};
/// use agen::timeline::{Handler, TextBlockEvent, TextBlockKind};
///
/// struct TextCollector {
/// texts: Vec<String>,
+42
View File
@@ -0,0 +1,42 @@
#![doc = include_str!("../README.md")]
mod engine;
mod handler;
mod message;
pub(crate) mod callback;
pub mod event;
pub mod interceptor;
pub mod llm_client;
pub mod providers;
pub mod prune;
pub mod state;
pub mod timeline;
pub mod token_counter;
pub mod tool;
pub mod tool_server;
pub mod usage_record;
pub use agen_macros::{description, tool, tool_registry};
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
pub use engine::{
Engine, EngineConfig, EngineError, EngineResult, EngineRunOutput, LlmRetryNotice,
ToolRegistryError,
};
pub use handler::ToolUseBlockStart;
pub use interceptor::Interceptor;
pub use message::{ContentPart, Item, Message, Role};
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
pub use usage_record::UsageRecord;
/// Implementation dependencies used by code generated from `agen` macros.
///
/// This module is not a stable user-facing API. It is public only because macro expansion
/// happens in the downstream crate.
#[doc(hidden)]
pub mod __private {
pub use async_trait;
pub use schemars;
pub use serde;
pub use serde_json;
}
@@ -30,7 +30,7 @@ pub enum AuthRequirement {
/// リクエスト毎に認証ヘッダを動的に組み立てるプロバイダ。
///
/// access token が refresh で更新されたり、複数ヘッダを同時に注入する
/// 必要があるケースで使う。実体は呼び出し側に置き、llm-engine
/// 必要があるケースで使う。実体は呼び出し側に置き、agen
/// trait を知るだけ。
///
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
@@ -8,7 +8,7 @@ use std::time::Duration;
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
///
/// `Default` は llm-engine 全体の固定値を返す。呼び出し側からの上書きが
/// `Default` は agen 全体の固定値を返す。呼び出し側からの上書きが
/// 必要になったら拡張する。
#[derive(Debug, Clone)]
pub struct RetryPolicy {
@@ -4,8 +4,8 @@
//! function_call item が first-class で、SSE イベントも `response.*` 名前空間で
//! 流れる。
//!
//! - リクエスト JSON 生成: [`request`]
//! - SSE イベントパース → [`Event`](crate::llm_client::event::Event) 変換: [`events`]
//! - リクエスト JSON 生成: `request`
//! - SSE イベントパース → [`Event`](crate::llm_client::event::Event) 変換: `events`
mod capability;
mod events;
@@ -69,7 +69,7 @@ impl fmt::Debug for RequestTrace {
/// # Examples
///
/// ```ignore
/// use llm_engine::Item;
/// use agen::Item;
///
/// let user = Item::user_message("Hello!");
/// let assistant = Item::assistant_message("Hi there!");
@@ -24,7 +24,7 @@ mod private {
/// # Examples
///
/// ```ignore
/// use llm_engine::Engine;
/// use agen::Engine;
///
/// let mut engine = Engine::new(client)
/// .system_prompt("You are helpful.");
@@ -348,7 +348,7 @@ where
/// # Examples
///
/// ```ignore
/// use llm_engine::{Timeline, Handler, TextBlockKind, TextBlockEvent};
/// use agen::{Timeline, Handler, TextBlockKind, TextBlockEvent};
///
/// struct MyHandler;
/// impl Handler<TextBlockKind> for MyHandler {
@@ -364,7 +364,7 @@ impl Default for ToolExecutionContext {
/// # Manual Implementation
///
/// ```ignore
/// use llm_engine::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
/// use agen::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
/// use std::sync::Arc;
///
/// struct MyTool { counter: std::sync::atomic::AtomicUsize }
@@ -8,13 +8,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use agen::Engine;
use agen::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use agen::llm_client::retry::RetryPolicy;
use agen::llm_client::{ClientError, LlmClient, Request, ResponseStream};
use agen::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use async_trait::async_trait;
use common::MockLlmClient;
use llm_engine::Engine;
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use llm_engine::llm_client::retry::RetryPolicy;
use llm_engine::llm_client::{ClientError, LlmClient, Request, ResponseStream};
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
#[derive(Clone)]
struct FailOnceClient {
@@ -221,7 +221,7 @@ impl Tool for FixedOutputTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Ok(self.output.clone())
}
@@ -260,6 +260,7 @@ async fn test_callback_tool_result_events() {
ToolOutput {
summary: "did the thing".into(),
content: Some("full detail body".into()),
attachments: Vec::new(),
},
));
@@ -296,7 +297,7 @@ impl Tool for ErroringTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Err(ToolError::ExecutionFailed(self.message.clone()))
}
@@ -6,11 +6,11 @@ use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use agen::llm_client::event::{BlockType, DeltaContent, Event};
use agen::llm_client::{ClientError, LlmClient, Request};
use agen::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use async_trait::async_trait;
use futures::Stream;
use llm_engine::llm_client::event::{BlockType, DeltaContent, Event};
use llm_engine::llm_client::{ClientError, LlmClient, Request};
use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -272,7 +272,7 @@ pub fn assert_timeline_integration(subdir: &str) {
});
for event in &events {
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
let timeline_event: agen::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}
@@ -9,10 +9,10 @@ use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use agen::Engine;
use agen::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use async_trait::async_trait;
use common::MockLlmClient;
use llm_engine::Engine;
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
/// Fixture directory path
fn fixtures_dir() -> std::path::PathBuf {
@@ -61,7 +61,7 @@ impl Tool for MockWeatherTool {
async fn execute(
&self,
input_json: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst);
@@ -102,7 +102,7 @@ fn test_mock_client_from_fixture() {
/// Creates a client with programmatically constructed events instead of using fixture files.
#[test]
fn test_mock_client_from_events() {
use llm_engine::llm_client::event::Event;
use agen::llm_client::event::Event;
// Specify events directly
let events = vec![
@@ -180,7 +180,7 @@ async fn test_engine_tool_call() {
/// Useful when test independence is needed and external file dependency should be eliminated.
#[tokio::test]
async fn test_engine_with_programmatic_events() {
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use agen::llm_client::event::{Event, ResponseStatus, StatusEvent};
// Construct event sequence programmatically
let events = vec![
@@ -208,8 +208,8 @@ async fn test_engine_with_programmatic_events() {
/// correctly extracts id, name, and input (JSON).
#[tokio::test]
async fn test_tool_call_collector_integration() {
use llm_engine::llm_client::event::Event;
use llm_engine::timeline::{Timeline, ToolCallCollector};
use agen::llm_client::event::Event;
use agen::timeline::{Timeline, ToolCallCollector};
// Event sequence containing ToolUse block
let events = vec![
@@ -225,7 +225,7 @@ async fn test_tool_call_collector_integration() {
// Dispatch events
for event in &events {
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
let timeline_event: agen::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}
@@ -8,12 +8,12 @@ mod common;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use agen::Item;
use agen::llm_client::event::{Event, ResponseStatus, StatusEvent};
use agen::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use agen::{Engine, EngineError};
use async_trait::async_trait;
use common::MockLlmClient;
use llm_engine::Item;
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_engine::{Engine, EngineError};
// =============================================================================
// Mutable State Tests
@@ -150,7 +150,7 @@ impl Tool for CountingTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(format!("{}-ok", self.name).into())
@@ -6,15 +6,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use async_trait::async_trait;
use llm_engine::Engine;
use llm_engine::interceptor::{
Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo,
};
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_engine::tool::{
use agen::Engine;
use agen::interceptor::{Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo};
use agen::llm_client::event::{Event, ResponseStatus, StatusEvent};
use agen::tool::{
Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult,
};
use async_trait::async_trait;
mod common;
use common::MockLlmClient;
@@ -64,7 +62,7 @@ impl Tool for SlowTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(self.delay_ms)).await;
@@ -468,7 +466,7 @@ async fn test_post_tool_call_modification() {
async fn execute(
&self,
_: &str,
_ctx: llm_engine::tool::ToolExecutionContext,
_ctx: agen::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Ok("Original Result".to_string().into())
}
@@ -565,7 +563,7 @@ async fn test_before_tool_call_synthetic_result_committed() {
assert_eq!(blocked_clone.call_count(), 0, "Blocked tool should not run");
assert!(result.engine.history().iter().any(|item| matches!(
item,
llm_engine::Item::ToolResult {
agen::Item::ToolResult {
call_id,
summary,
is_error: true,
@@ -13,13 +13,13 @@
mod common;
use common::MockLlmClient;
use llm_engine::Engine;
use llm_engine::Item;
use llm_engine::llm_client::event::{
use agen::Engine;
use agen::Item;
use agen::llm_client::event::{
BlockMetadata, BlockStart, BlockStop, BlockType, Event, ReasoningBlockData, ResponseStatus,
StatusEvent,
};
use common::MockLlmClient;
fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Event> {
vec![
@@ -28,9 +28,9 @@ fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Eve
block_type: BlockType::Thinking,
metadata: BlockMetadata::Thinking,
}),
Event::BlockDelta(llm_engine::llm_client::event::BlockDelta {
Event::BlockDelta(agen::llm_client::event::BlockDelta {
index: 100,
delta: llm_engine::llm_client::event::DeltaContent::Thinking(text.into()),
delta: agen::llm_client::event::DeltaContent::Thinking(text.into()),
}),
Event::BlockStop(BlockStop {
index: 100,
@@ -174,7 +174,7 @@ async fn injected_reasoning_survives_into_outgoing_request() {
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use llm_engine::llm_client::{ClientError, LlmClient, Request};
use agen::llm_client::{ClientError, LlmClient, Request};
/// Request を 1 度だけキャプチャして空ストリームを返す client。
#[derive(Clone)]
@@ -5,12 +5,7 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
// Imports needed for macro expansion
use schemars;
use serde;
use llm_engine::ToolExecutionContext;
use llm_engine_macros::tool_registry;
use agen::{ToolExecutionContext, tool_registry};
// =============================================================================
// Test: Basic Tool Generation
@@ -3,15 +3,15 @@
//! Retry/backoff は Engine の lifecycle 管理に属するため、transport は 1 回だけ
//! request を送り、HTTP status / Retry-After を `ClientError` に載せて返す。
use agen::llm_client::LlmClient;
use agen::llm_client::auth::AuthRequirement;
use agen::llm_client::capability::ModelCapability;
use agen::llm_client::error::ClientError;
use agen::llm_client::event::Event;
use agen::llm_client::scheme::Scheme;
use agen::llm_client::transport::{HttpTransport, ResolvedAuth};
use agen::llm_client::types::Request;
use futures::StreamExt;
use llm_engine::llm_client::LlmClient;
use llm_engine::llm_client::auth::AuthRequirement;
use llm_engine::llm_client::capability::ModelCapability;
use llm_engine::llm_client::error::ClientError;
use llm_engine::llm_client::event::Event;
use llm_engine::llm_client::scheme::Scheme;
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::llm_client::types::Request;
use serde_json::Value;
use std::time::Duration;
use wiremock::matchers::{method, path};

Some files were not shown because too many files have changed in this diff Show More